Перейти к основному содержимому

Доставка Lockbox секретов при помощи Docker-compose init container

· 3 мин. чтения

В прошлом посте я рассказал, как доставить на виртуальную машину секреты из 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 не завершится успешно.

Полный пример можно посмотреть в репозитории.