ConfigMap

В kubernetes есть объект ConfigMap, который хранит конфигурации для других объектов куба. ConfigMap может быть использован в случаях, когда мы хотим:

  • Во внутрь контейнера прокинуть файл с конфигурацией для нашего приложения, через read-only volume;
  • Добавить переменные окружения во внутрь контейнера;
  • Передать в контейнер агрументы командной строки.

Мы можем хранить любую информацию в манифесте конфигмапы (кроме паролей и etc..), в кластере создаем новый configmap-объект и описываем его конфигурацию, в поле data. Далее включаем объект в деплоймент, поле template: -> containers:. При запуске пода, кубернетес примонтирует конфигмап как volume.

Теперь на практике, рассмотрим каждый из этих кейсов.

ConfigMap, для прокидывания файла.

Создадим новый манифест, где пропишем конфигурации для nginx. Далее примонтируем объект с конфигой в наш deployment.

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
data:
  default.conf: |
    server {
      listen 80 default_server;
      server_name _;
    	
      default_type text/plain;

      location / {
        return 200 '$hostname\n';
      }
    }

Первые директивы нам уже знакомы, мы указываем версию api и тип создаваемого объекта - ConfigMap. В метаданных указываем поле - name c именем конфигмапы. Добавляется новое поле - data. В этом поле в качестве ключа указывается имя файла - default.conf, в значение через пайп (|) прописываем конфиг для nginx сервера.

Применяем манифест, добавляем конфигурацию:

$ kubectl apply -f nginx-config.yml
---
configmap/nginx-config created

Напишем новый деплоймент с добавлением configmap:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-config
spec:
  replicas: 2
  selector:
    matchLabels:
      depl: nginx-conf
  strategy:
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 2
    type: RollingUpdate
  template:
    metadata:
      labels:
        depl: nginx-conf
    spec:
      containers:
      - image: nginx:1.17
        name: nginx
        ports:
        - containerPort: 80
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /
            port: 80
          successThreshold: 1
          periodSeconds: 10
          timeoutSeconds: 3
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /
            port: 80
          successThreshold: 1
          periodSeconds: 10
          timeoutSeconds: 3
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
          limits:
            cpu: 150m
            memory: 150Mi
        volumeMounts:
        - name: nginx-configs
          mountPath: /etc/nginx/conf.d
      volumes:
      - name: nginx-configs
        configMap:
          name: nginx-config

Сам deployment манифест разбирался в предыдущих главах, обратим внимание на конец yml-файла. На одном уровне с полем containers добавилось поле volumes, это отдельная сушность которая описывает все тома в этой деплойменте. Далее идет название тома - name: nginx-configs, созданный из объекта configMap, а имя configmap - name: nginx-config.

Подключаем этот том к контейнеру, в конце описания контейнера добавилось новое поле volumeMounts, в котором перечисляются подключаемые тома. В этом поле мы указываем имя volume - name: nginx-configs. И путь куда монтируется раздел внутри контейнера - mountPath: /etc/nginx/conf.d. Запускаем деплоймет и заходим во внутрь контейнера, что бы пролистить примонтированную конфигурацию:

$ kubectl apply -f deployment-config.yml
$ kubectl exec -ti deployment-config-84b475d9f5-rb2vp bash

Находясь внутри контейнера, дергаем примапленую конфигу nginx:

root@deployment-config-84b475d9f5-rb2vp:/# cat /etc/nginx/conf.d/default.conf 
server {
  listen 80 default_server;
  server_name _;
	
  default_type text/plain;

  location / {
    return 200 '$hostname\n';
  }
}

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

Поменяем конфигмапу, что бы проверить как новый конфиг применится к подам. Для этого юзаем команду (cm - сокращенно ConfigMap):

$ kubectl edit cm nginx-config
--
return 200 '$hostname\\nOKE\\n';

В локейшене nginx, в конец строки добавил слово.

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

$ kubectl exec -ti deployment-config-84b475d9f5-rb2vp bash
---                   
root@deployment-config-84b475d9f5-rb2vp:/# cat /etc/nginx/conf.d/default.conf 
server {
  listen 80 default_server;
  server_name _;
	
  default_type text/plain;

  location / {
    return 200 '$hostname\nOKE\n';
  }
}

Как видно из вывода изменения вступили в силу.

Мы можем постучаться курлом на наш под, но для начала нужно пробросить порт:

$ kubectl port-worward my-deployment-6f968d7c9c-dwbwr 20001:80
$ curl localhost:20001
---
my-deployment-6f968d7c9c-dwbwr

Есть один момент, для того что бы nginx увидел изменившийся конфиг нужно перезапустить контейнер. Работает это так, по причине того что nginx не может динамически перечитывать свою конфигурацию и нужно перезапустить процесс.

ConfigMap, для передачи переменных окружения.

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

$ vim my-pod-env.yml 
---
apiVersion: v1
kind: Pod
metadata:
  name: my-pod-env
spec: 
  containers:
  - image: nginx:1.20
    name: nginx-env
    ports:
    - containerPort: 80
    env:
    - name: NGINX_VERSION
      value: "1.20"
    - name: NGINX_PROXY
      value: "backend01"

Проникаем во внутрь контейнера, и выводим список наших переменных.

$ kubectl exec -ti my-pod-env bash
---
root@my-pod-env:/# printenv | grep -i nginx
NGINX_PROXY=backend01
NGINX_VERSION=1.20

Данный подход хоть и работает, но не совсем применим когда у нас имется несколько сред (prod, dev, stage) и нужно придерживаться одинакового окружения для всех сред. Данный кейс решается, использованием ConfigMap, одну конфигурацию мы применяем для каждой из сред.

Такой тип конфигмапы можем создать через описания манифеста, или с использованием литералов:

$ kubectl create configmap nginx-enc-lit --from-literal=NGX_PROXY=backend1 --from-literal=NGX_VERSION=1.20 
--
$ kubectl get cm nginx-enc-lit -o yaml
apiVersion: v1
data:
  NGX_PROXY: backend1
  NGX_VERSION: "1.20"
kind: ConfigMap
metadata:
  creationTimestamp: "2022-12-07T08:35:08Z"
  name: nginx-enc-lit
  namespace: default
  resourceVersion: "51791"
  uid: 902cbb9c-5ca7-464b-9825-812ba0f55edf

Теперь создаем манифест для пода, и включаем конфигмапу:

$ cat my-pod-envfile.yml
---
apiVersion: v1
kind: Pod
metadata: 
  name: my-pod-envfile
spec: 
  containers:
  - image: nginx:1.18
    name: nginx
    ports:
    - containerPort: 80
    envFrom:
    - configMapRef:
        name: nginx-enc-lit  

В контексте описания контейнера, перечисляем конфигмапы в поле - envFrom:, через директиву configMapRef с указанием имени конфигмапы, подключаем нашу конфигурацию - nginx-enc-lit. Запускаем под, и смотрим переменные:

$ kubectl exec -ti my-pod-envfile printenv | grep -i ngx
---
NGX_PROXY=backend1
NGX_VERSION=1.20

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

env:
  - name: MY_VAR
    valueFrom:
      configMapRef:
        name: my-configmap-app
        key: var_key

В поле env, явно указываем имя переменной для окружения. Через valueFrom: - указываем откуда берем данные. В нашем случае это - configMapRef:, далее указывает имя конфигмапы и ключ.

Secret’s

В Kubernetes есть аналогичный ConfigMap объект - Secrets (Секреты). Отличительной разницей является то, что в секретах содержимое ключей, из поля data шифруются кодировкой base64. Теперь пароли, токены и секретные файлы для наших приложений мы можем изолировать в секретах, и не хранить их в гите или же не каждый раз добавлять в image. Ну и стоит отметить, что любой секрет можно раскодировать командой - base64. С помошью секретов, мы также можем прокинуть во внутрь контейнера в файлы с конфидециальной информацией, файлы монтируются в read-only tmpfs-каталог. Или же передать записи секретов в качестве переменных среды.

Итак, на практике посмотрим два кейса использования:

Secrets, добавление секретов в виде файлов в томе.

Произведем ситуацию, условно мы хотим что бы клиента нашего веб-сервера обслуживались поверх HTTPS. Для этого создадим сертификат и закрытый ключ. Закрытый ключ должен быть зашищен, поэтому положим его в секреты.

Для начала локально создаем сертификат и прикатный ключ.

$ openssl genrsa -out mydev.key 2048
$ openssl req -new -x509 -key mydev.key -out mydev.crt -days 365 -subj /CN=mydev.local

Сертификат и ключ есть, создаем новый secretmap, создавать будем через утилиту kubectl, далее посмотрим как бы выглядил манифест.

$ kubectl create secret generic mydev-https-certs --from-file=mydev.key --from-file=mydev.crt
---
secret/mydev-https-certs created

Пролистим содержимое созданного секрета:

tony@i3Arch:~/Documents/minikube-sandbox/secrets-configmaps » kubectl get secrets mydev-https-certs -o yaml     apiVersion: v1
data:
  mydev.crt: LS0tLS1CRUdJTiBDRVJUSUZJ......
  mydev.key: LS0tLS1CRUdJTiBQUklWQVKN......
kind: Secret
metadata:
  creationTimestamp: "2022-12-11T14:47:06Z"
  name: mydev-https-certs
  namespace: default
  resourceVersion: "240752"
  uid: 2e0dc781-317b-41bf-ab99-028618803b0b
type: Opaque

В поле data появилось два ключа - имена файлов сертификата и ключа. В значении ключей - mydev.crt, mydev.key закодированное base64 содержимое, одноименных файлов. Из за большого количества строк в файлах сертифика и ключа, удобно создавать секрет через kubectl c использованием ключа --from-file. Иначе мы могли бы ручным способом создать манифест, предварительно закодировав содержимое исходных файлов.

Сразу же отметим, что объект secrets бывает нескольких типов:

  • generic - используется для хранения настроек в формате ключ:значение. Здесь храним пароли, токены, etc…;
  • docker-registry - это специальный тип секретов, в которых храняться параметры подключения к приватный docker-репозиториям;
  • tls - здесь хранятся tls сертификаты, обычно для использования в ingress.

Создадим новый nginx конфиг, с указанием порты и путей к ключу и сертификату.

server {
	listen 80;
	listen 443 ssl;
	
	server_name _;

	ssl_certificate certs/mydev.crt;
	ssl_certificate_key certs/mydev.key;

	ssl_protocols	TLSv1.1 TLSv1.2;
	ssl_ciphers	HIGH:!aNULL:!MD5;

	default_type text/plain;

	location / {
		return 200 'Hello, I am $hostname\n';
	}
}

Теперь нужно создать новый конфиг мапу для нашего приложения:

$ kubectl create cm mydev-https-conf --from-file=nginx-mydev.conf

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

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mydev-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: mydev
  strategy:
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 2
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: mydev
    spec:
      containers:
      - image: nginx:1.20
        name: nginx
        ports:
        - containerPort: 80
        - containerPort: 443
        readinessProbe:
          failureThreshold: 3
          successThreshold: 1
          periodSeconds: 15
          timeoutSeconds: 5
          httpGet:
            port: 80
            path: /
        livenessProbe:
          failureThreshold: 3
          successThreshold: 1
          periodSeconds: 15
          timeoutSeconds: 5
          httpGet: 
            port: 80
            path: /
        resources:
          requests:
            cpu: 200m
            memory: 250Mi
          limits:
            cpu: 200m
            memory: 250Mi
        volumeMounts:
        - name: nginx-conf
          mountPath: /etc/nginx/conf.d/
        - name: nginx-certs
          mountPath: /etc/nginx/certs/
      volumes:
      - name: nginx-certs
        secret:
          secretName: mydev-https-certs
      - name: nginx-conf
        configMap:
          name: mydev-https-conf

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

  • Раздел с сертификатом и ключем:

      - name: nginx-certs
        secret:
          secretName: mydev-https-certs
    
    десь указываем имя раздела, далее говорится что используемый тип раздела - `secret`. В конце указывается имя используемого секрета.
    
    
    
  • Раздел с конфигом:

      - name: nginx-conf
        configMap:
          name: mydev-https-conf
    
    о аналогии с прошлой темой, указываем имя раздела - `name: nginx-conf`. Говорим, что тип раздела - `configMap:` и указываем имя конфигмапы. 
    
    

В блоке описания контейнера создаем точки монтирования - mountPoints. Указываем имя volume, и путь к какому каталогу монтируем внутри контейнера.

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

$ kubectl exec -ti mydev-nginx-847b7679d5-hrj4g bash
--
root@mydev-nginx-847b7679d5-hrj4g:/# ls -l /etc/nginx/certs/
lrwxrwxrwx 1 root root 16 Dec 11 16:06 mydev.crt -> ..data/mydev.crt
lrwxrwxrwx 1 root root 16 Dec 11 16:06 mydev.key -> ..data/mydev.key
--
root@mydev-nginx-847b7679d5-hrj4g:/# cat /etc/nginx/conf.d/nginx-mydev.conf 
server {
	listen 80;
	listen 443 ssl;
	
	server_name _;

	ssl_certificate certs/mydev.crt;
	ssl_certificate_key certs/mydev.key;

	ssl_protocols	TLSv1.1 TLSv1.2;
	ssl_ciphers	HIGH:!aNULL:!MD5;

	default_type text/plain;

	location / {
		return 200 'Hello, I am $hostname\n';
	}
}

Давайте пробросим порт до контейнера и попробуем выдернуть самоподписной сертификат:

$ kubectl port-forward mydev-nginx-847b7679d5-hrj4g 8443:443
$ curl https://localhost:8443 -k -v
---
* Server certificate:
*  subject: CN=mydev.local
*  start date: Dec 11 14:41:41 2022 GMT
*  expire date: Dec 11 14:41:41 2023 GMT
*  issuer: CN=mydev.local
*  SSL certificate verify result: self-signed certificate (18), continuing anyway.
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
...
< HTTP/1.1 200 OK

Secrets, добавление переменных окружения.

Для реализации это примера, создадим новый секрет с указанием наших переменных. Далее обновим deployment.

Через утилиту kubectl, создаем новый секрет с указанием литералов:

$ kubectl create secret generic nginx-env --from-literal user=user1
---
secret/nginx-env created

Обновляем манифест деплоймента. В контексте описания контейнера добавляем поле env:

env:
  - name: USER
    valueFrom:
      secretKeyRef:
        name: nginx-env
        key: user

Здесь указываем имя переменной name: USER, и источник - valueFrom. В последующем указывается из какого объекта берется значение - secretKeyRef, имя секрета - name: nginx-env и ключ - key: user.

Применяем обновленный деплоймент, и смотрим результат:

tony@i3Arch:~/Documents/minikube-sandbox/secrets-configmaps » kubectl apply -f mydev-deployment.yml
tony@i3Arch:~/Documents/minikube-sandbox/secrets-configmaps » kubectl exec -ti mydev-nginx-5bb8ffd7d6-jhqw6 printenv | grep -i user
--
USER=user1

Дополнительные ссылки: