Как добавить новый сервис?
Сервисы условно можно разделить на внутренние и внешние.
Внутренними можно назвать сервисы внутри репозитория infra, применимые, как правило, для остальных сервисов.
Внешними сервисами можно назвать любые сервисы, находящиеся за пределами текущего репозитория и текущего каталога.
Соответственно, наиболее актуальный вопрос — как добавить новый внешний сервис?
Как добавить новый внешний сервис?
Подсказка
Для создания нового сервиса можно воспользоваться существующими файлами:
Конфигурация сервиса задаётся в файле docker-compose.swarm.yml.
Этот файл задаёт конфигурацию в формате docker-compose.
Пример docker-compose.swarm.yml для сервиса domain
version: "3.9"
services:
domain-server:
image: ${REGISTRY_HOST}/domain-server:latest
volumes:
- ./configs:/data/conf
- ./storage:/storage
logging:
driver: "json-file"
options:
max-size: 10m
max-file: "3"
tag: "{{.ImageName}}|{{.Name}}|{{.ID}}"
deploy:
labels:
traefik.enable: "true"
traefik.backend: domain
traefik.http.routers.domain.entrypoints: https
traefik.http.routers.domain.tls: "true"
traefik.http.routers.domain.tls.certresolver: letsencrypt
traefik.http.routers.domain.rule: Host(`domain.${SUBDOMAIN}.${DOMAIN}`)
traefik.http.services.domain.loadbalancer.server.port: 8000
placement:
constraints:
- "node.labels.cluster==swarm"
mode: replicated
replicas: 1
update_config:
parallelism: 1
order: start-first
failure_action: rollback
delay: 10s
rollback_config:
parallelism: 0
order: stop-first
restart_policy:
condition: any
delay: 5s
max_attempts: 3
window: 120s
domain-integrator:
image: ${REGISTRY_HOST}/domain-integrator:latest
volumes:
- ./configs:/data/conf
- ./storage:/storage
logging:
driver: "json-file"
options:
max-size: 10m
max-file: "3"
tag: "{{.ImageName}}|{{.Name}}|{{.ID}}"
deploy:
labels:
traefik.enable: "false"
placement:
constraints:
- "node.labels.cluster==swarm"
mode: replicated
replicas: 1
update_config:
parallelism: 1
order: start-first
failure_action: rollback
delay: 10s
rollback_config:
parallelism: 0
order: stop-first
restart_policy:
condition: any
delay: 5s
max_attempts: 3
window: 120s
domain-notifier:
image: ${REGISTRY_HOST}/domain-notifier:latest
volumes:
- ./configs:/data/conf
- ./storage:/storage
logging:
driver: "json-file"
options:
max-size: 10m
max-file: "3"
tag: "{{.ImageName}}|{{.Name}}|{{.ID}}"
deploy:
labels:
traefik.enable: "false"
placement:
constraints:
- "node.labels.cluster==swarm"
mode: replicated
replicas: 1
update_config:
parallelism: 1
order: start-first
failure_action: rollback
delay: 10s
rollback_config:
parallelism: 0
order: stop-first
restart_policy:
condition: any
delay: 5s
max_attempts: 3
window: 120s
domain-docs:
image: ${REGISTRY_HOST}/domain-docs:latest
logging:
driver: "json-file"
options:
max-size: 10m
max-file: "3"
tag: "{{.ImageName}}|{{.Name}}|{{.ID}}"
deploy:
labels:
traefik.enable: "true"
traefik.backend: domain-docs
traefik.http.routers.domain-docs.entrypoints: https
traefik.http.routers.domain-docs.tls: "true"
traefik.http.routers.domain-docs.tls.certresolver: letsencrypt
traefik.http.routers.domain-docs.rule: Host(`docs.domain.${SUBDOMAIN}.${DOMAIN}`)
traefik.http.services.domain-docs.loadbalancer.server.port: 80
placement:
constraints:
- "node.labels.cluster==swarm"
mode: replicated
replicas: 1
update_config:
parallelism: 1
order: start-first
failure_action: rollback
delay: 10s
rollback_config:
parallelism: 0
order: stop-first
restart_policy:
condition: any
delay: 5s
max_attempts: 3
window: 120s
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
interval: 30s
timeout: 3s
retries: 12
Помимо основных ключей конфигурации, используются ключи logging, deploy и healthcheck.
Секция logging
Параметр logging отвечает за формат формирования логов (позже по ним можно будет искать в Loki).
logging yaml example
logging:
driver: "json-file"
options:
max-size: 10m
max-file: "3"
tag: "{{.ImageName}}|{{.Name}}|{{.ID}}"
Формат тега задан в виде {{.ImageName}}|{{.Name}}|{{.ID}} для корректного парсинга в promtail.
Пример конфигурации promtail.yml, с секцией отвечающей за парсинг логов
# ...
scrape_configs:
# ...
- job_name: containers
# ...
pipeline_stages:
# ...
- regex:
# expression set by tag: "{{.ImageName}}|{{.Name}}|{{.ID}}"
expression: ^(?P<image>([^|]+))\|(?P<service>([^|]+))\|(?P<container_id>([^|]+))$
source: "tag"
- labels:
docker_image: image
docker_service: service
docker_container_id: container_id
docker_timestamp: timestamp
Документация секции logging — Docker Docs | JSON File logging driver.
Секция deploy
Параметр deploy отвечает за развёртывание сервиса в кластере Docker Swarm:
Пример описания секции deploy
deploy:
labels:
traefik.enable: "true"
traefik.backend: domain-docs
traefik.http.routers.domain-docs.entrypoints: https
traefik.http.routers.domain-docs.tls: "true"
traefik.http.routers.domain-docs.tls.certresolver: letsencrypt
traefik.http.routers.domain-docs.rule: Host(`docs.domain.${SUBDOMAIN}.${DOMAIN}`)
traefik.http.services.domain-docs.loadbalancer.server.port: 80
placement:
constraints:
- "node.labels.cluster==swarm"
mode: replicated
replicas: 1
update_config:
parallelism: 1
order: start-first
failure_action: rollback
delay: 10s
rollback_config:
parallelism: 0
order: stop-first
restart_policy:
condition: any
delay: 5s
max_attempts: 3
window: 120s
Для вынесения сервиса на домен или поддомен используется секция deploy.labels.
Шаблон секции labels при добавления нового сервиса
labels:
traefik.enable: "true"
traefik.backend: {{service_name}}
traefik.http.routers.{{service_name}}.entrypoints: https
traefik.http.routers.{{service_name}}.tls: "true"
traefik.http.routers.{{service_name}}.tls.certresolver: letsencrypt
traefik.http.routers.{{service_name}}.rule: Host(`{{service_name}}.${SUBDOMAIN}.${DOMAIN}`)
traefik.http.services.{{service_name}}.loadbalancer.server.port: {{service_port}}
Для примера выше вместо {{service_name}} можно указать имя сервиса и вместо {{service_port}} номер порта, который
должен быть выведен наружу.
В таком случае, на https://{{service_name}}.${SUBDOMAIN}.${DOMAIN} (на стандартный для TLS порт 443) будет выведен
указанный порт сервиса.
Описание секции deploy — Docker Docs | Compose Deploy Specification.
Секция healthcheck
Используется для проверки работоспособности сервиса.
Уместно при новом развёртывании сервиса — трафик не будет переключаться на новый сервис, пока у него не пройдёт healthcheck.
Таким образом, наличие секции healthcheck позволяет деплоить сервисы бесшовно.
Пример секции healthcheck для абстрактного сервиса
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
interval: 30s
timeout: 3s
retries: 12
Пример секции healthcheck для backend-сервиса на базе go-kratos
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:8000/healthcheck || exit 1
interval: 30s
timeout: 3s
retries: 12
Примечание
wget вместо curl используется потому, что wget встречается в образах чаще.
Например, он включён в состав образа alpine.
Описание секции — Docker Docs | healthcheck.
Dockerfile-*
Сам по себе сервис может состоять из нескольких приложений-сервисов.
Такие "суб-сервисы" задаются с помощью файлов Dockerfile-<service>.
??? Пример файла Dockerfile-server
```dockerfile
FROM golang:1.19 AS builder
COPY . /src
WORKDIR /src
RUN make build
FROM alpine:latest
COPY --from=builder /src/bin /app
WORKDIR /app
EXPOSE 8000
EXPOSE 9000
EXPOSE 13000
VOLUME /data/conf
CMD ["./server", "-conf", "/data/conf"]
```
В Makefile.extended.mk можно встретить секции, работающие с такими файлами.
Пример команды make push
.PHONY: push
# Build and push image to registry
push:
@set -e; for service in $$(ls Dockerfile-* | cut -c 12-); \
do docker build -t ${REGISTRY_HOST}/${SERVICE_NAME}-$${service}:latest \
-f ${CURRENT_DIRECTORY}/Dockerfile-$${service} ${CURRENT_DIRECTORY}/. \
&& docker push ${REGISTRY_HOST}/${SERVICE_NAME}-$${service}:latest ; \
done
Наличие одновременно и docker-compose.swarm.yml, и Dockerfile-* может запутать.
В чём разница?
Файл docker-compose.swarm.yml:
- Нацелен на запуск сервиса и его "суб-сервисов";
- Описывает конфигурацию для запуска сервиса (на целевом сервере от всего репозитория иногда нужен только этот файл, исходный код не участвует в развёртывании);
- Опирается как на внешние образы, так и на внутренние образы, формируемые с помощью файлов
Dockerfile-*и загружаемые вregistryсозданного кластера.
Файл Dockerfile-*:
- Нацелен на создание Docker-образов для "суб-сервисов";
- Описывает команды для сборки образа нужного "суб-сервиса";
- Собранный с его помощью образ можно загрузить в
registyrсозданного кластера.
Как добавить новый внутренний сервис?
В большинстве случаев, нужно повторить логику существующих внутренних сервисов:
- Создать каталог, который называется также, как и сервис
- В каталоге разместить
docker-compose.swarm.yaml - Настроить по аналогии с другими файлами
docker-compose.swarm.yaml - Учесть сервис в
Makefile— если он должен запускаться вместе с кластером, то добавить название сервиса в командыmake run-onlyиmake down