company_banner

Как расширить приложение в Kubernetes при помощи мультиконтейнерных подов: основные рекомендации

Автор оригинала: Emanuel Evans
  • Перевод


Запустить облачные микросервисы или 12-факторные приложения в Kubernetes относительно просто. Но как насчет запуска приложений, которые явно не предназначены для работы в контейнерной среде?


Команда Kubernetes as a Service Mail.ru Cloud Solutions перевела статью об одном из самых мощных инструментов в Kubernetes — мультиконтейнерном поде. Он позволяет менять поведение приложения, не изменяя его кода. Эта функция удобна для приложений, которые изначально не предназначены для работы в контейнерах.


Итак, посмотрим на примере.


Защита HTTP


Elasticsearch разработали до того, как контейнеры стали популярны (хотя сейчас его можно запустить в Kubernetes). И его можно рассматривать в качестве замены для устаревшего Java-приложения, которое предназначено для работы на виртуальной машине.


В этой статье я буду использовать Elasticsearch в качестве примера приложения, которое нужно улучшить с помощью мультиконтейнерных подов.


Вот очень простой, но не для продакшена, деплой и сервис для Elasticsearch:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: elasticsearch
  template:
    metadata:
      labels:
        app.kubernetes.io/name: elasticsearch
    spec:
      containers:
        - name: elasticsearch
          image: elasticsearch:7.9.3
          env:
            - name: discovery.type
              value: single-node
          ports:
            - name: http
              containerPort: 9200
---
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
spec:
  selector:
    app.kubernetes.io/name: elasticsearch
  ports:
    - port: 9200
      targetPort: 9200

Примечание: переменная окружения dicsovery.type нужна, чтобы запустить одиночную реплику.


Elasticsearch слушает HTTP-порт 9200 по умолчанию.


Вы можете убедиться, что под работает, запустив в кластере другой под и подключившись к службе elasticsearch:


$ kubectl run -it --rm --image=curlimages/curl curl \
  -- curl http://elasticsearch:9200

{
  "name" : "elasticsearch-77d857c8cf-mk2dv",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "z98oL-w-SLKJBhh5KVG4kg",
  "version" : {
    "number" : "7.9.3",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "c4138e51121ef06a6404866cddc601906fe5c868",
    "build_date" : "2020-10-16T10:36:16.141335Z",
    "build_snapshot" : false,
    "lucene_version" : "8.6.2",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

Теперь предположим, что вы хотите получить модель безопасности с нулевым доверием и зашифровать весь трафик в сети. Как поступить, если в приложении нет встроенной поддержки TLS?


Примечание: последние версии Elasticsearch поддерживают TLS, раньше эта функция была платной.


Первая мысль — выполнить терминацию TLS при помощи Nginx Ingress, поскольку Ingress маршрутизирует внешний трафик в кластере. Это не соответствует требованиям: трафик между подом входящего трафика и подом Elasticsearch может проходить по сети в незашифрованном виде.



Внешний трафик проходит через Ingress и направляется к подам



Если вы завершите TLS на входе, остальной трафик не будет зашифрован


Решение, которое соответствует требованиям, — прикрепить прокси-контейнер Nginx к поду, доступному по TLS. Тогда трафик будет зашифрован на всем пути от пользователя к поду.



Если вы включаете прокси-контейнер в модуль, то можете завершить TLS в модуле Nginx



До контейнера Elasticsearch трафик идет в зашифрованном виде


Вот как выглядит деплоймент:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: elasticsearch
  template:
    metadata:
      labels:
        app.kubernetes.io/name: elasticsearch
    spec:
      containers:
        - name: elasticsearch
          image: elasticsearch:7.9.3
          env:
            - name: discovery.type
              value: single-node
            - name: network.host
              value: 127.0.0.1
            - name: http.port
              value: '9201'

       - name: nginx-proxy
          image: nginx:1.19.5
          volumeMounts:
            - name: nginx-config
              mountPath: /etc/nginx/conf.d
              readOnly: true
            - name: certs
              mountPath: /certs
              readOnly: true
          ports:
            - name: https
              containerPort: 9200

     volumes:
        - name: nginx-config
          configMap:
            name: elasticsearch-nginx
        - name: certs
          secret:
            secretName: elasticsearch-tls
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: elasticsearch-nginx
data:
  elasticsearch.conf: |
    server {
        listen 9200 ssl;
        server_name elasticsearch;
        ssl_certificate /certs/tls.crt;
        ssl_certificate_key /certs/tls.key;

        location / {
            proxy_pass http://localhost:9201;
        }
    }

Давайте немного поясню:


  • Elasticsearch слушает localhost на порту 9201 вместо значения по умолчанию 0.0.0.0:9200. Для этого предусмотрены переменные среды network.host и http.port.
  • Новый контейнер nginx-proxy слушает порт 9200 HTTPS и передает запросы в Elasticsearch в порт 9201. Секрет elasticsearch-tls содержит сертификат и ключ TLS, их можно сгенерировать с помощью cert-manager.

Таким образом, запросы извне пода поступают в Nginx через порт 9200 HTTPS, а затем попадают в Elasticsearch через порт 9201.



Вы можете проверить, что все работает, отправив HTTPS-запрос из кластера:


kubectl run -it --rm --image=curlimages/curl curl \
  -- curl -k https://elasticsearch:9200

{
  "name" : "elasticsearch-5469857795-nddbn",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "XPW9Z8XGTxa7snoUYzeqgg",
  "version" : {
    "number" : "7.9.3",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "c4138e51121ef06a6404866cddc601906fe5c868",
    "build_date" : "2020-10-16T10:36:16.141335Z",
    "build_snapshot" : false,
    "lucene_version" : "8.6.2",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

Примечание: ключ -k нужен для самоподписанных сертификатов TLS. В продакшене необходимо использовать доверенный сертификат.


Беглый взгляд на логи показывает, что запрос прошел через прокси Nginx:


kubectl logs elasticsearch-5469857795-nddbn nginx-proxy | grep curl

10.88.4.127 - - [26/Nov/2020:02:37:07 +0000] "GET / HTTP/1.1" 200 559 "-" "curl/7.73.0-DEV" "-"

Вы также можете убедиться: нельзя подключиться к Elasticsearch через незашифрованные соединения.


kubectl run -it --rm --image=curlimages/curl curl \
  -- curl http://elasticsearch:9200

<html>
<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<center>The plain HTTP request was sent to HTTPS port</center>
<hr><center>nginx/1.19.5</center>
</body>
</html>

Поздравляю, вы включили TLS, не меняя кода Elasticsearch или образа в контейнере!


Практика добавления прокси-контейнера в под распространена, поэтому у нее есть свое имя — шаблон Ambassador.


Примечание: Все шаблоны в этом посте подробно описаны в отличной статье от Google.


Добавление базовой поддержки TLS — только начало. Вот еще несколько вещей, которые можно сделать с шаблоном Ambassador:


  1. Чтобы зашифровать весь трафик в кластере с помощью сертификата TLS, устанавливают прокси-сервер Nginx (или другой) в каждом поде кластера. Можно пойти еще дальше и использовать взаимный TLS, он гарантирует — все запросы аутентифицированы и зашифрованы. Это основной подход, в котором используют сервисные сети, например, Istio или Linkerd.
  2. Можно использовать прокси, он гарантирует, что центр OAuth аутентифицирует все запросы, проверяя jwt. Одним из примеров — gcp-iap-auth, он проверяет, что запросы аутентифицированы GCP Identity-Aware Proxy.
  3. Можно подключиться к внешней базе данных через безопасный туннель. Это особенно удобно для баз данных без встроенной поддержки TLS, например для старых версий Redis. Другой пример — Google Cloud SQL Proxy.

Принцип работы мультиконтейнерных подов


Давайте вернемся назад и посмотрим, в чем разница между подами и контейнерами в Kubernetes. Это поможет лучше понять происходящее под капотом.


Традиционный контейнер, например запущенный при помощи Docker, обеспечивает несколько форм изоляции:


  • Изоляция ресурсов, например ограничение памяти.
  • Изоляция процессов.
  • Файловая система и изоляция монтирования.
  • Сетевая изоляция.

Примечание: Docker позволяет настраивать и другие вещи, но эти — наиболее важные.


Инструменты под капотом — пространства имен Linux и контрольные группы (cgroups).


Контрольные группы — удобный способ ограничить ресурсы, которые может использовать конкретный процесс, например CPU или память. Допустим, вы указываете: процесс может использовать только 2 ГБ памяти и одно из четырех ядер CPU.


В то же время пространства имен отвечают за изоляцию процесса и ограничивают то, что он может видеть. Например, процесс может видеть только сетевые пакеты, непосредственно с ним связанные. И не может видеть все сетевые пакеты, проходящие через сетевой адаптер.


Другой пример — вы можете изолировать файловую систему и заставить процесс поверить, что у него есть доступ ко всей системе.



Начиная с версии ядра 5.6, есть восемь видов пространств имен, и mount — одно из них



Используя пространство имен mount, вы заставляете процесс поверить, что ему доступны все каталоги на хосте, хотя это не так



Пространство имен mount предназначено для изоляции ресурсов, в данном случае — файловой системы



Каждый процесс видит одну и ту же файловую систему, но при этом изолирован от других


Примечание: если вам нужно освежить в памяти контрольные группы и пространства имен, то вот отличная статья, в которой рассматриваются некоторые технические детали.


В Kubernetes контейнер обеспечивает все перечисленные формы изоляции, кроме изоляции сети.


Изоляция сети происходит на уровне пода. Другими словами: у каждого контейнера в поде — своя файловая система, таблица процессов и так далее. Но все они совместно используют одно и то же сетевое пространство имен.


Чтобы лучше понять, как все работает, давайте поиграемся с простым подом, состоящим из нескольких контейнеров:


apiVersion: v1
kind: Pod
metadata:
  name: podtest
spec:
  containers:

   - name: c1
      image: busybox

     command: ['sleep', '5000']
      volumeMounts:
        - name: shared
          mountPath: /shared

   - name: c2
      image: busybox

     command: ['sleep', '5000']
      volumeMounts:
        - name: shared
          mountPath: /shared
  volumes:
    - name: shared
      emptyDir: {}

Немного разберем:


  • Есть два контейнера, какое-то время оба просто спят.
  • Есть том emptyDir, по сути, это временный локальный том, он действует в течение всего срока службы пода.
  • Том emptyDir монтируется в каждом контейнере в каталоге /shared.

Вы можете убедиться, что том смонтирован в первом контейнере, используя kubectl exec:


$ kubectl exec -it podtest --container c1 -- sh

Команда подключает терминальный сеанс к контейнеру c1 в поде podtest. Вы можете проверить прикрепленные тома с1 с помощью команды:


$ mount | grep shared

/dev/vda1 on /shared type ext4 (rw,relatime)

Как видите, том смонтирован на /shared — общем томе, который мы создали ранее. Теперь создадим несколько файлов:


$ echo "foo" > /tmp/foo

$ echo "bar" > /shared/bar

Проверим те же файлы из второго контейнера. Сначала подключимся к нему с помощью команды:


$ kubectl exec -it podtest --container c2 -- sh

$ cat /shared/bar

bar

cat /tmp/foo
cat: can't open '/tmp/foo': No such file or directory

Как видите, файл, созданный в общем каталоге, доступен в обоих контейнерах, а файл в /tmp — нет. Так случилось, поскольку за исключением тома, файловые системы контейнеров полностью изолированы друг от друга.


Теперь посмотрим на сеть и изоляцию процессов. Простой способ понять, как настроена сеть, — использовать команду ip link, она показывает сетевые устройства системы Linux.


Выполним команду в первом контейнере:


$ kubectl exec -it podtest -c c1 -- ip link

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

178: eth0@if179: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue
    link/ether 46:4c:58:6c:da:37 brd ff:ff:ff:ff:ff:ff

А теперь ту же команду в другом контейнере:


$ kubectl exec -it podtest -c c2 -- ip link

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

178: eth0@if179: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue
    link/ether 46:4c:58:6c:da:37 brd ff:ff:ff:ff:ff:ff

Вы можете видеть, что в обоих контейнерах есть:


  • Один и тот же девайс eth0.
  • Один и тот же MAC-адрес 46:4c:58:6c:da:37.

Предполагается, что MAC-адреса глобально уникальны, значит, это явный признак того, что модули используют одно и то же устройство.


Теперь давайте посмотрим на совместное использование сети в действии. Подключимся к первому контейнеру с помощью команды:


$ kubectl exec -it podtest -c c1 -- sh

Запустим очень простой сетевой листенер nc:


$ nc -lk -p 5000 127.0.0.1 -e 'date'

Команда запускает листенер на локальном хосте на порту 5000 и показывает дату любому подключенному TCP-клиенту. Может ли к нему подключиться второй контейнер? Откроем терминал во втором контейнере с помощью команды:


$ kubectl exec -it podtest -c c2 -- sh

Посмотрите сами — второй контейнер может подключиться к сетевому порту, но не видит процесс nc:


$ telnet localhost 5000

Connected to localhost
Sun Nov 29 00:57:37 UTC 2020
Connection closed by foreign host

$ ps aux

PID   USER     TIME  COMMAND
    1 root      0:00 sleep 5000
   73 root      0:00 sh
   81 root      0:00 ps aux

Подключившись через telnet, вы можете увидеть дату, это доказывает, что листенер nc работает. Но ps aux, показывающий все процессы в контейнере, вообще не отображает nc. Это связано с тем, что у контейнеров внутри модуля есть изоляция процессов, но нет изоляции сети.


И это объясняет, как работает паттерн Ambassador:


  • Поскольку все контейнеры используют одно и то же сетевое пространство имен, то один контейнер может прослушивать все подключения — даже внешние.
  • Остальные контейнеры принимают соединения только от localhost, отклоняя любые внешние соединения.

Контейнер, который получает внешний трафик, называют Ambassador — отсюда и название шаблона.



Примечание: поскольку сетевое пространство имен общее, то несколько контейнеров в поде не могут прослушивать один и тот же порт!


Давайте посмотрим на другие варианты использования многоконтейнерных подов.


Предоставление метрик по стандартному интерфейсу


Допустим, вы используете Prometheus для мониторинга всех сервисов в вашем кластере Kubernetes. Но есть приложения, которые изначально не экспортируют метрики Prometheus, например Elasticsearch.


Можете ли вы добавить метрики Prometheus в свои поды, не меняя код приложения? Можете, если используете шаблон адаптера.


В примере с Elasticsearch давайте добавим в под контейнер «экспортер», он предоставляет метрики Elasticsearch в формате Prometheus. Это легко сделать, поскольку для Elasticsearch есть экспортер с открытым исходным кодом (вам нужно добавить соответствующий порт в сервис).


apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: elasticsearch
  template:
    metadata:
      labels:
        app.kubernetes.io/name: elasticsearch
    spec:
      containers:
        - name: elasticsearch
          image: elasticsearch:7.9.3
          env:
            - name: discovery.type
              value: single-node
          ports:
            - name: http
              containerPort: 9200
        - name: prometheus-exporter
          image: justwatch/elasticsearch_exporter:1.1.0
          args:
            - '--es.uri=http://localhost:9200'
          ports:
            - name: http-prometheus
              containerPort: 9114
---
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
spec:
  selector:
    app.kubernetes.io/name: elasticsearch
  ports:
    - name: http
      port: 9200
      targetPort: http
    - name: http-prometheus
      port: 9114
      targetPort: http-prometheus

Как только вы примените этот манифест, метрики станут доступны на порту 9114.


$ kubectl run -it --rm --image=curlimages/curl curl \
  -- curl -s elasticsearch:9114/metrics | head

# HELP elasticsearch_breakers_estimated_size_bytes Estimated size in bytes of breaker
# TYPE elasticsearch_breakers_estimated_size_bytes gauge
elasticsearch_breakers_estimated_size_bytes{breaker="accounting",name="elasticsearch-ss86j"} 0
elasticsearch_breakers_estimated_size_bytes{breaker="fielddata",name="elasticsearch-ss86j"} 0
elasticsearch_breakers_estimated_size_bytes{breaker="in_flight_requests",name="elasticsearch-ss86j"} 0
elasticsearch_breakers_estimated_size_bytes{breaker="model_inference",name="elasticsearch-ss86j"} 0
elasticsearch_breakers_estimated_size_bytes{breaker="parent",name="elasticsearch-ss86j"} 1.61106136e+08
elasticsearch_breakers_estimated_size_bytes{breaker="request",name="elasticsearch-ss86j"} 16440
# HELP elasticsearch_breakers_limit_size_bytes Limit size in bytes for breaker
# TYPE elasticsearch_breakers_limit_size_bytes gauge

Еще раз — вы можете изменить поведение приложения, фактически не меняя код или образы контейнеров. В данном случае мы предоставили стандартизированные метрики Prometheus, которые используют кластерные инструменты (такие, как оператор Prometheus). И таким образом добились хорошего разделения между приложением и базовой инфраструктурой.


Просмотр логов


Давайте посмотрим на паттерн Sidecar, с его помощью в под добавляют улучшающий приложение контейнер.


Шаблон Sidecar — общий и применяется в различных вариантах использования. Так, вы можете услышать, что любые контейнеры в модуле, кроме первого, называют «sidecars».


Сначала рассмотрим один из классических вариантов использования — log tailing sidecar.


В контейнерной среде рекомендуют всегда писать в стандартный вывод, чтобы собирать и агрегировать журналы логов централизованно. Но многие старые приложения разработаны для логирования в файлы, и изменить это иногда нетривиальная задача. Благодаря log tailing sidecar вам, возможно, не придется ничего менять!


Вернемся к примеру с Elasticsearch. Он немного надуманный, поскольку контейнер Elasticsearch по умолчанию ведет журнал в стандартном режиме, и нетривиально заставить его писать в файл.


Вот как выглядит деплой:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
  labels:
    app.kubernetes.io/name: elasticsearch
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: elasticsearch
  template:
    metadata:
      labels:
        app.kubernetes.io/name: elasticsearch
    spec:
      containers:
        - name: elasticsearch
          image: elasticsearch:7.9.3
          env:
            - name: discovery.type
              value: single-node
            - name: path.logs
              value: /var/log/elasticsearch
          volumeMounts:
            - name: logs
              mountPath: /var/log/elasticsearch
            - name: logging-config
              mountPath: /usr/share/elasticsearch/config/log4j2.properties
              subPath: log4j2.properties
              readOnly: true
          ports:
            - name: http
              containerPort: 9200

       - name: logs
          image: alpine:3.12
          command:
            - tail
            - -f
            - /logs/docker-cluster_server.json
          volumeMounts:
            - name: logs
              mountPath: /logs
              readOnly: true
     volumes:
        - name: logging-config
          configMap:
            name: elasticsearch-logging
        - name: logs
          emptyDir: {}

Примечание: Файл конфигурации ведения журнала логов — отдельный файл ConfigMap, он слишком большой, чтобы включать его сюда.


У обоих контейнеров есть общий том с именем logs. Контейнер Elasticsearch записывает журналы на этот том, а контейнер журналов логов — читает из соответствующего файла и выводит его в стандартный формат.


Вы можете получить поток журнала логов, указав нужный контейнер в kubectl logs:


$ kubectl logs elasticsearch-6f88d74475-jxdhl logs | head

{
  "type": "server",
  "timestamp": "2020-11-29T23:01:42,849Z",
  "level": "INFO",
  "component": "o.e.n.Node",
  "cluster.name": "docker-cluster",
  "node.name": "elasticsearch-6f88d74475-jxdhl",
  "message": "version[7.9.3], pid[7], OS[Linux/5.4.0-52-generic/amd64], JVM"
}
{
  "type": "server",
  "timestamp": "2020-11-29T23:01:42,855Z",
  "level": "INFO",
  "component": "o.e.n.Node",
  "cluster.name": "docker-cluster",
  "node.name": "elasticsearch-6f88d74475-jxdhl",
  "message": "JVM home [/usr/share/elasticsearch/jdk]"
}
{
  "type": "server",
  "timestamp": "2020-11-29T23:01:42,856Z",
  "level": "INFO",
  "component": "o.e.n.Node",
  "cluster.name": "docker-cluster",
  "node.name": "elasticsearch-6f88d74475-jxdhl",
  "message": "JVM arguments [...]"
}

Самое прекрасное в Sidecar: потоковая передача в стандартный вывод — не единственный вариант. Если нужно переключиться на настраиваемую службу агрегации журналов логов, то можно просто изменить контейнер Sidecar, ничего не меняя в приложении.


Другие примеры использования Sidecars


Есть множество вариантов использования Sidecars, контейнер логов — только один и довольно простой пример.


Вот еще несколько вариантов, которые могут вам пригодиться:



Подготовка пода к запуску


Во всех примерах многоконтейнерных подов, которые мы рассмотрели выше, несколько контейнеров работают одновременно.


Еще в Kubernetes можно запускать контейнеры инициализации — контейнеры, которые выполняются и завершаются до запуска «обычных» контейнеров. Это позволяет запустить сценарий инициализации до того, как под запустится полностью.


Почему иногда нужно выполнять инициализацию в отдельном контейнере, а не, допустим, добавлять инициализацию в сценарий точки входа вашего контейнера?


Давайте посмотрим на примере Elasticsearch. В документации Elasticsearch рекомендуют устанавливать параметр sysctl vm.max_map_count в продакшен развертываниях. В контейнерных средах это проблематично — для sysctl нет изоляции на уровне контейнера, и любые изменения происходят на уровне узла.


Что делать с этим, если вы не можете настраивать узлы Kubernetes? Один из способов — запустить Elasticsearch в привилегированном контейнере. Это позволяет Elasticsearch изменять системные настройки на своем хост-узле и сценарий точки входа для добавления sysctl.


Но это чрезвычайно опасно с точки зрения безопасности! Если служба Elasticsearch будет скомпрометирована, то злоумышленник получил бы root-доступ к своему хост-узлу.


Чтобы снизить этот риск, можно использовать контейнер инициализации:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: elasticsearch
  template:
    metadata:
      labels:
        app.kubernetes.io/name: elasticsearch
    spec:

     initContainers:
        - name: update-sysctl
          image: alpine:3.12
          command: ['/bin/sh']
          args:
            - -c
            - |
              sysctl -w vm.max_map_count=262144
          securityContext:
            privileged: true

     containers:
        - name: elasticsearch
          image: elasticsearch:7.9.3
          env:
            - name: discovery.type
              value: single-node
          ports:
            - name: http
              containerPort: 9200

Под устанавливает sysctl в привилегированном контейнере инициализации, после чего контейнер Elasticsearch запускается, как и ожидалось. Вы по-прежнему используете привилегированный контейнер, что не идеально. Но он, по крайней мере, маленький и работает недолго, так что поверхность атаки намного ниже.


Примечание: этот подход рекомендуют в Elastic Cloud Operator.


Использование привилегированного контейнера инициализации для подготовки узла к запуску пода — распространенный шаблон. Например, Istio использует контейнеры инициализации для настройки правил iptables при каждом запуске пода.


Еще одна причина использовать контейнер инициализации — подготовить файловую систему пода. Один из распространенных вариантов использования — управление секретами.


Еще варианты использования контейнера инициализации


Если для управления секретами вместо секретов Kubernetes вы используете что-то вроде HashicCorp Vault, то можете получить секреты в контейнере инициализации и сохранить их на общем томе emptyDir.


apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app.kubernetes.io/name: myapp
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: myapp
  template:
    metadata:
      labels:
        app.kubernetes.io/name: myapp
    spec:

     initContainers:
        - name: get-secret
          image: vault
          volumeMounts:
            - name: secrets
              mountPath: /secrets
          command: ['/bin/sh']
          args:
            - -c
            - |
              vault read secret/my-secret > /secrets/my-secret

     containers:
        - name: myapp
          image: myapp
          volumeMounts:
            - name: secrets
              mountPath: /secrets
      volumes:
        - name: secrets
          emptyDir: {}

Теперь секрет secret/my-secret доступен в файловой системе для контейнера myapp.


Это основная идея того, как работают системы вроде инжектора Sidecar Vault Agent. Однако на практике они намного сложнее: комбинируют изменяющие веб-хуки, контейнеры инициализации и вспомогательные Sidecar.


Вот еще несколько причин использовать контейнер инициализации:


  • Вы хотите, чтобы сценарий миграции базы данных запускался перед вашим приложением. Обычно это можно сделать в сценарии точки входа, но с помощью специального контейнера иногда проще.
  • Вы хотите получить большой файл из S3 или GCS, от которого зависит ваше приложение. В данному случае контейнер инициализации помогает избежать раздувания контейнера вашего приложения.

Заключение


Мы рассмотрели много вариантов, так что вот таблица с шаблонами использования. В ней перечислены основные шаблоны и варианты их использования:


Обязательно прочтите официальную документацию и исходный документ с дизайном контейнеров, если хотите разобраться в теме.


Вариант использования Ambassador Adapter Sidecar Init
Шифрование и/или аутентификация входящих запросов
Подключение к внешним ресурсам через безопасный туннель
Предоставление метрик в стандартизированном формате, например в Prometheus
Потоковая передача логов из файла в агрегатор логов
Добавление локального кэша Redis в свой под
Мониторинг и перезагрузка ConfigMaps в реальном времени
Вставка в приложение секретов из Vault
Изменение настроек уровня узла с помощью привилегированного контейнера
Получение файлов из S3 до запуска приложения

Новым пользователям платформы мы дарим 3000 бонусов после полной верификации аккаунта. Вы сможете попробовать Kubernetes aaS или другие наши сервисы на реальных проектах.

Что еще почитать:


  1. 11 факапов уровня PRO при внедрении в Kubernetes и как их избежать.
  2. Как жили до Kubernetes: сравниваем самый популярный оркестратор с другими решениями.
  3. Наш Телеграм-канал Вокруг Kubernetes в Mail.ru Group.
Mail.ru Group
Строим Интернет

Комментарии 0

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое