9.2 KiB
9.2 KiB
Kraken — Portainer Access & Cloudflared SSH Tunnel
Статус: ⏳ cloudflared pending deploy
Portainer
| Параметр | Значение |
|---|---|
| URL | http://localhost:9000 |
| Auth header | X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g= |
| Endpoint ID | уточнить: `curl -s -H "X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" http://localhost:9000/api/endpoints |
Portainer API — Quick Reference
BASE=http://localhost:9000
KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g="
EP=1
# Список контейнеров
curl -s -H "X-API-Key: $KEY" "$BASE/api/endpoints/$EP/docker/containers/json?all=true" \
| jq '[.[] | {name: .Names[0], status: .Status, image: .Image}]'
# Логи контейнера
curl -s -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/<ID>/logs?stdout=1&stderr=1&tail=50"
# Удалить контейнер
curl -s -X DELETE -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/<ID>"
Временный Alpine-контейнер (выполнить команду на хосте)
Для разовых задач — alpine с bind /:/host:
BASE=http://localhost:9000
KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g="
EP=1
ID=$(curl -s -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
"$BASE/api/endpoints/$EP/docker/containers/create?name=alpine-tmp-$$" \
-d '{
"Image": "alpine",
"Cmd": ["sh", "-c", "YOUR_COMMAND_HERE && echo DONE"],
"HostConfig": {"Binds": ["/:/host"], "AutoRemove": false}
}' | jq -r '.Id')
curl -s -X POST -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$ID/start"
sleep 2
curl -s -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$ID/logs?stdout=1&stderr=1"
curl -s -X DELETE -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$ID"
⚠️
AutoRemove: false— обязательно, иначе контейнер удалится до получения логов ⚠️ Использоватьalpine, неalpine/git— у git-образа свой ENTRYPOINT
Cloudflared — деплой (выполняет Кракен)
CF Tunnel уже создан Alex'ом (kraken в Zero Trust), hostname настроен: kraken.qentra.top → SSH → localhost:22.
CF Tunnel Token:
eyJhIjoiZjE...TnpFNU5USTNNREl4WkRVeCJ9
Шаг 1 — Убедиться что EP = 1
curl -s -H "X-API-Key: ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g=" \
http://localhost:9000/api/endpoints | jq '[.[].Id]'
Если не 1 — запомнить реальный ID и использовать его вместо 1 в шагах ниже.
Шаг 2 — Создать и запустить cloudflared контейнер
BASE=http://localhost:9000
KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g="
EP=1
TOKEN="eyJhIjoiZjE0YTM3NWQ3NmU1ZTU4M2IzNjZhMDJhYjUyNDZiNDciLCJ0IjoiMmRkZDJiMjgtNjYwYy00NjQ0LTk1Y2ItY2Y1ZTQzNTBhMTA1IiwicyI6Ik5HVTRZalZtTURVdFptSm1OeTAwWVdGakxUaGlaamd0WmpFMFlUazVOekt3WTJWaCJ9"
CID=$(curl -s -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
"$BASE/api/endpoints/$EP/docker/containers/create?name=cloudflared" \
-d "{
\"Image\": \"cloudflare/cloudflared:latest\",
\"Cmd\": [\"tunnel\", \"--no-autoupdate\", \"run\"],
\"Env\": [\"TUNNEL_TOKEN=$TOKEN\"],
\"HostConfig\": {
\"RestartPolicy\": {\"Name\": \"unless-stopped\"},
\"NetworkMode\": \"host\"
}
}" | jq -r '.Id')
echo "Container ID: $CID"
curl -s -X POST -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$CID/start"
echo "Started."
Шаг 3 — Проверить логи
BASE=http://localhost:9000
KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g="
EP=1
CID=$(curl -s -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/json" \
| jq -r '.[] | select(.Names[] | contains("cloudflared")) | .Id')
curl -s -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$CID/logs?stdout=1&stderr=1&tail=30" \
| strings
Ожидаем:
INF Registered tunnel connection connIndex=0 ... protocol=http2
INF Registered tunnel connection connIndex=1 ...
INF Registered tunnel connection connIndex=2 ...
INF Registered tunnel connection connIndex=3 ...
Все 4 соединения = туннель активен. Если видишь ERR — проверить токен.
Шаг 4 — Сохранить compose-файл (опционально, для удобства в будущем)
BASE=http://localhost:9000
KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g="
EP=1
TOKEN="eyJhIjoiZjE0YTM3NWQ3NmU1ZTU4M2IzNjZhMDJhYjUyNDZiNDciLCJ0IjoiMmRkZDJiMjgtNjYwYy00NjQ0LTk1Y2ItY2Y1ZTQzNTBhMTA1IiwicyI6Ik5HVTRZalZtTURVdFptSm1OeTAwWVdGakxUaGlaamd0WmpFMFlUazVOekt3WTJWaCJ9"
COMPOSE="services:\n cloudflared:\n image: cloudflare/cloudflared:latest\n container_name: cloudflared\n restart: unless-stopped\n command: [\"tunnel\", \"--no-autoupdate\", \"run\"]\n environment:\n - TUNNEL_TOKEN=$TOKEN\n network_mode: host"
ID=$(curl -s -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
"$BASE/api/endpoints/$EP/docker/containers/create?name=alpine-cf-setup" \
-d "{
\"Image\": \"alpine\",
\"Cmd\": [\"sh\", \"-c\", \"mkdir -p /host/home/kraken/cloudflared && printf '$COMPOSE' > /host/home/kraken/cloudflared/docker-compose.yml && echo DONE\"],
\"HostConfig\": {\"Binds\": [\"/:/host\"], \"AutoRemove\": false}
}" | jq -r '.Id')
curl -s -X POST -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$ID/start"
sleep 2
curl -s -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$ID/logs?stdout=1&stderr=1"
curl -s -X DELETE -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$ID"
Шаг 5 — Создать скилл по внешнему доступу
mkdir -p /opt/data/skills/devops/kraken-external-access
cat > /opt/data/skills/devops/kraken-external-access/SKILL.md << 'SKILLEOF'
---
name: kraken-external-access
category: devops
description: Внешний доступ к Kraken через Cloudflare Tunnel — SSH с Eagle, Portainer API, управление cloudflared.
triggers:
- "доступ к кракену снаружи"
- "ssh к кракену с орла"
- "управление docker на кракене удалённо"
- "cloudflared крякен"
---
# Kraken — External Access via Cloudflare Tunnel
## SSH с Eagle (уже настроено в ~/.ssh/config на Eagle)
```bash
ssh kraken.qentra.top
Portainer API
BASE=http://localhost:9000
KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g="
EP=1
# Список контейнеров
curl -s -H "X-API-Key: $KEY" "$BASE/api/endpoints/$EP/docker/containers/json?all=true" \
| jq '[.[] | {name: .Names[0], status: .Status}]'
Управление cloudflared
# Статус + логи
CID=$(curl -s -H "X-API-Key: $KEY" "$BASE/api/endpoints/$EP/docker/containers/json" \
| jq -r '.[] | select(.Names[] | contains("cloudflared")) | .Id')
curl -s -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$CID/logs?stdout=1&stderr=1&tail=30" | strings
# Рестарт
curl -s -X POST -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$CID/restart"
Питфолы
network_mode: host— cloudflared видит все локальные порты Кракена напрямуюrestart: unless-stopped— при падении поднимается автоматически- Токен в env
TUNNEL_TOKEN— не менять без пересоздания контейнера - Статус туннеля: https://one.dash.cloudflare.com/ → Networks → Tunnels → kraken
- Если туннель не поднимается — искать
ERRв логах cloudflared
Связанные заметки
family/how-to/kraken-portainer-access.md— полная инструкция по деплоюfamily/how-to/openmediavault-rpi5.md— структура Docker на Кракене SKILLEOF echo "Скилл создан."
> ⚠️ `/opt/data/` — HDD-volume, скилл переживёт rebuild образа
### Шаг 6 — Обновить статус в этой заметке
Заменить первую строку `> Статус: ⏳ cloudflared pending deploy` на `> Статус: ✅ cloudflared запущен`.
---
## SSH-доступ с Eagle (после деплоя)
Добавить в `~/.ssh/config` на Eagle:
Host kraken.qentra.top ProxyCommand cloudflared access ssh --hostname %h User kraken IdentityFile ~/.ssh/id_rsa StrictHostKeyChecking no
Затем: `ssh kraken.qentra.top`
> `cloudflared` на Eagle уже установлен (используется для `zulip.qentra.top`)
---
## Связанные заметки
- [[openmediavault-rpi5]] — настройка Kraken (Docker, Hermes, структура папок)
- [[truenas-infrastructure]] — Taiga (аналогичный Portainer API подход)
- [[obsidian-sync]] — синхронизация хранилища.