Доставка Lockbox секретов при помощи Docker-compose init container
В прошлом посте я рассказал, как доставить на виртуальную машину секреты из Lockbox при помощи скрипта в user-data. В этом посте я расскажу, как сделать это при помощи Docker-compose init container. Это решение можно использовать вместе с Container Optimized Solution, чтобы доставить секреты в контейнеры. Но это не обязательное условие, вы можете использовать его в любом проекте, где используется Docker-compose.
Docker-compose init container
Docker-compose позволяет запускать контейнеры в определенном порядке. Это удобно, когда один контейнер зависит от другого.
Например, приложение зависит от базы данных и не может запуститься, пока база данных не будет готова. Для этого можно
использовать depends_on
в docker-compose.yml
.
При запуске Compose не ждет, пока контейнер будет "готов", достаточно, что он запущен. Это может вызвать проблемы, если, например, у вас есть система реляционных баз данных, которая должна запустить свои собственные службы, прежде чем сможет обрабатывать входящие соединения.
Для этого можно использовать атрибут condition
с одним из следующих вариантов:
service_started
— базовое поведение, когда контейнер считается готовым к использованию, когда он запущен.service_healthy
— это значение указывает, что зависимость должна быть «здоровой», что определяется с помощью healthcheck, перед запуском зависимого сервиса.service_completed_successfully
— зависимость должна успешно завершиться перед запуском зависимого сервиса.
В этом примере я покажу, как использовать service_completed_successfully
для того, чтобы сначала получить секреты из
Lockbox, а затем запустить приложение.
Пример
Dockerfile для init container
FROM alpine:3.21
WORKDIR /
RUN apk --update add ca-certificates wget jq bash curl
ENV HOME=/
ENV PATH=/yandex-cloud/bin:$PATH
# Install Yandex Cli
RUN curl https://storage.yandexcloud.net/yandexcloud-yc/install.sh | bash
Возьмем alpine образ и установим YC CLI. Дополнительно установим несколько утилит, которые могут пригодиться для работы.
Curl
пригодится для установки YC CLI, jq
для работы с JSON и bash
для выполнения скриптов.
Демо сервер
Для демонстрации я возьму простой сервер на Go. Он будет возвращать значение из конфига, который мы получим из Lockbox.
package main
import (
"encoding/json"
"io"
"log"
"net/http"
"os"
)
type Config struct {
Response string `json:"response"`
}
func main() {
// Read the configuration file
file, err := os.Open("/etc/demo/config.json")
if err != nil {
log.Fatalf("Failed to open config file: %v", err)
}
defer file.Close()
// Parse the configuration file
configData, err := io.ReadAll(file)
if err != nil {
log.Fatalf("Failed to read config file: %v", err)
}
var config Config
if err := json.Unmarshal(configData, &config); err != nil {
log.Fatalf("Failed to parse config file: %v", err)
}
// Set up the HTTP server
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(config.Response))
})
log.Println("Server is running on port 8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
}
Мы будем читать конфиг из файла /etc/demo/config.json
в строке 17. В нем будет храниться значение response
.
В строках 35-37
мы возвращаем это значение в ответе сервера.
Docker-compose
Docker-compose файл будет выглядеть следующим образом, так как я использую Terraform для развертывания, в файле есть переменные, которые будут подставлены при выполнении.
version: "3.7"
volumes:
# Use this volume to mount the configuration file into the app container
app-config: {}
services:
app:
image: ${app_image}
depends_on:
init:
condition: service_completed_successfully
volumes:
- app-config:/etc/demo
ports:
- "8080:8080"
init:
image: ${init_image}
command: bash -c "yc lockbox payload get --id ${secret_id} --key config > /etc/demo/config.json"
volumes:
- app-config:/etc/demo
На что нужно обратить внимание:
2-4
— Глобальный параметр volumes
, нужен для того, чтобы определить volume, который может быть использован в
нескольких сервисах. app-config
— это volume, который будет использован в init
и app
сервисах.
15-19
— Сервис init
, который будет запущен первым. Мы используем depends_on
для того, чтобы сервис app
не
стартовал, до тех пор, пока сервис init
не завершится успешно. В command
мы запускаем скрипт, который получает
секреты из Lockbox и записывает их в файл. Команда yc lockbox payload get
получает секрет по id
и key
и записывает
его в файл /etc/demo/config.json
. Важно, чтобы команда была указана не напрямую, а через bash -c
, чтобы можно было
использовать знакомый синтаксис bash, например, >
для перенаправления вывода в файл.
10
— В этой строке мы используем condition: service_completed_successfully
, чтобы сервис app
не стартовал, пока
сервис init
не завершится успешно.
Полный пример можно посмотреть в репозитории.