This is an old revision of the document!


https://ansilh.com/01-introduction/
https://ansilh.com/15-k8s_from_sratch/

Материалов про kubernetes очень много. В процессе обучения просто я фиксирую для себя то что считаю важным. Это не тутораил и не гайд. Это просто мои заметки.
https://kubernetes.io/docs/reference/kubectl/cheatsheet/

Docker

Создаем файл Dockerfile:

FROM node:7
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]

В одной директории с Dockerfile размещаем файл приложения app.js и собираем:

docker build -t kubia .

Смотрим что контейнер собрался:

docker images
docker run --name kubia-container -p 8080:8080 -d kubia

В результате будет запущен новый контейнер с именем kubia-container из образа kubia. Контейнер будет отсоединен от консоли (флаг -d), а порт 8080 на локальной машине будет увязан с портом 8080 внутри контейнера.

docker ps
docker inspect kubia-container
docker exec -it kubia-container bash  
* **-i** убеждается, что STDIN держится открытым для ввода команд в оболочку;
* **-t** выделяет псевдотерминал (TTY).
docker stop kubia-container
docker rm kubia-container

Образ контейнера может иметь несколько тегов:

docker tag kubia luksa/kubia
docker push luksa/kubia
docker run -p 8080:8080 -d luksa/kubia
docker logs <container_ID>

Запуск приложения в среде kubernetes

kubectl run kubia --image=luksa/kubia --port=8080 --generator=run/v1

kubectl run создаст все необходимые компоненты без необходимости декларировать компоненты с помощью JSON или YAML.
–generator=run/v1 - нужен для того, чтобы текущая версия kubernetes не создавала Deployment, а создала ReplicationController. В результате будет создан Pod с одним контейнером kubia.
При выполнении команды произошло следующее:

  • команда kubectl создала в кластере новый объект контроллера репликации (ReplicationController)
  • контроллер репликации создал новый Pod, который планировщик запланировал для одного из рабочих узлов.
  • Агент Kubelet на этом узле увидел, что модуль был назначен рабочему узлу, и поручил платформе Docker выгрузить указанный образ из хранилища, После скачивания образа платформа Docker создала и запустила контейнер.

Список pods в дефолтном namespace:

kubectl get pods

Список pods в заданном namespace:

kubectl -n nsname get po

Список pods во всех неймспейсах:

kubectl get po -A

Вывод списка Pod'ов с дополнительной информацией:

kubectl get pods -o wide

Для доступа к приложению используются объекты Service. Они привязывают динамически разворачиваемые экземпляры приложения к статичным IP-адресам и портам:

kubectl expose rc kubia --type=LoadBalancer --name kubia-http 

В данном случае - создается объект Service с именем kubia-http типа LoadBalancer, который указывает на экземпляры приложения, за которыми следит ReplicatioController (rc) с именем kubia.

kubectl get services 

или

kubectl get svc

Увеличить количество запущенных реплик Pod'а на контроллере репликации:

kubectl scale rc kubia --replicas=3
kubectl describe rc kubia
kubectl create -f filename.yml

Внутри кластера kubernetes между подами (Pods) сеть плоская. Без шлюзов и трансляциий (nat-snat).

Под - Pod

apiVersion: v1                     # Версия API
kind: Pod                          # Тип объекта - pod
metadata:                          # Начало секции метаданных
  name: kubia-manual               # имя Pod'a
spec:                              # начало секции спецификации
  containers:                      # начало секции, описывающей контейнеры pod'а
  – image: luksa/kubia             # начало описания первого контейнера. используется образ luksa/kubia 
    name: kubia                    # Имя контейнера в pod'е
    ports:                         # секция портов, используемых приложением в контейнере
    – containerPort: 8080          # номер порта
      protocol: TCP                # используемый протокол
kubectl create -f kubia-manual.yaml
kubectl delete po kubia-gpu

Или с помощью селектора по метке:

kubectl delete po -l creation_method=manual
kubectl logs kubia-manual

Если в pod'е больше одного контейнера, то увидеть логи конкретного контейнера можно так:

kubectl logs kubia-manual -c kubia

Иногда для отладки нужно просто сделать так, чтобы порт pod'а стал доступен на локальной машине. Без создания service. Это делается так:

kubectl port-forward kubia-manual 8888:8080

В результате приложение pod'а (порт 8080) станет доступно на локальной машине на порте 8888.

Метки

Метки могут быть созданы для многих объектов. В примерах рассматриваются pod'ы.

Метки позволяют разделять pod'ы по некоторому признаку и обращаться к ним.

kubectl label po kubia-manual creation_method=manual

Метки могут быть прописаны в секции метаданных:

apiVersion: v1
kind: Pod
metadata:
  name: kubia-manual-v2
  labels:
    creation_method: manual
    env: prod

При изменении существующих меток нужно использовать параметр –overwrite.

kubectl label po kubia-manual-v2 env=debug --overwrite
kubectl get po --show-labels
kubectl get po -l creation_method,env
kubectl get po -l '!env',creation_method
kubectl get po -l creation_method=manual
kubectl get po -l creation_method!=manual

Устанавливаем метку на узел:

kubectl label node gke-kubia-85f6-node-0rrx gpu=true

Описываем привязку в декларации pod'а:

apiVersion: v1
kind: Pod
metadata:
  name: kubia-gpu
spec:
  nodeSelector:
    gpu: "true"                #селектор узла с меткой gpu
  containers:
  – image: luksa/kubia
    name: kubia

Или к узлу с заданным именем:

apiVersion: v1
kind: Pod
metadata:
  name: kubia-gpu
spec:
  nodeSelector:
    kubernetes.io/hostname: "desired.node.hostname"                #селектор узла по его hostname
  containers:
  – image: luksa/kubia
    name: kubia

Пространства имен (namespaces)

kubectl get ns
kubectl get po --namespace kube-syste
kubectl create namespace custom-namespace

Или декларативно с помощью файла yaml:

apiVersion: v1
kind: Namespace
metadata:
  name: custom-namespace

и затем

kubectl create -f custom-namespace.yaml
kubectl create -f kubia-manual.yaml -n custom-namespace
kubectl  config  set-context $(kubectl config currentcontext) --namespace custom-namespace

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

alias  kcd='kubectl  config  set-context $(kubectl config currentcontext) --namespace '

прописать его в ~/.bash.rc и затем переключаться между пространствами имен с помощью

kcd some-namespace
kubectl delete ns custom-namespace

Удаление pod'ов

kubectl delete po --all

Удаление почти всех ресурсов из неймспейса:

kubectl delete all --all




















Глоссарий

  • Kubernetes - система оркестрации контейнеров docker.
  • Namespace - это способ логического деления запускаемых в кластере сущностей.
  • Nodes (node.md): Нода это машина в кластере Kubernetes.
  • Pods (pods.md): Pod это группа контейнеров с общими разделами, запускаемых как единое целое. Обычно Pod объединет контейнеры, которые всместе выполняют некоторую функцию и не имеют смысла друг без друга. Например, в рамках pod'а процессы разных контейнеров могут общаться друг с другом по сети, используя адреса 127.0.0.1:port. То есть процессы различных контейнеров работают в едином сетевом неймспейсе pod'а.
  • ReplicaSet - набор экземпляров (реплик) какого-то pod'а. ReplicaSet - это более функциональные Replication Controllers. Сейчас они сосуществуют, но в дальнейшем останутся только ReplicaSet.
  • Deployment - набор экземпляров pod'а, для которого можно произвести обновление. При обновлении экземпляры будут по одному замещаться новыми версиями (rolling update). Таким образом реализуется zero downtime при обновлениях. Описывается с помощью yml-файла, содержащего спецификации (spec).
  • Services (services.md): Сервис в Kubernetes это абстракция (описывается yml-файлом) которая определяет логический объединённый набор pod и политику доступа к ним. Например - сервис типа LoadBalancer балансирует нагрузку между подами деплоймента. В простейшем случае - сервис просто пробрасывает порт для доступа к приложению.
  • Volumes (volumes.md): Volume(раздел) это директория, возможно, с данными в ней, которая доступна в контейнере.
  • Labels (labels.md): Label'ы это пары ключ/значение которые прикрепляются к объектам, например pod'ам. Label'ы могут быть использованы для создания и выбора наборов объектов.
  • Kubectl Command Line Interface (kubectl.md): kubectl интерфейс командной строки для управления Kubernetes.
  • Config Map - yml-файлик, содержащий некоторую конфигурационную информацию (например описание сервера nginx). В дальнейшем, в описании контейнера прописывается volumeMount, с указанием куда смонтируется volume, а также сам Volume, который ссылается на configmap. В результате - содержимое Config Map монтируется в контейнер в файлик в заданную директорию. Инструкции для монтирования содержатся в описании deployment'а или pod'а.

Архитектура

Программыне компоненты Kubernetes можно разделить на две части:

  • kubelet - компоненты, которые работают на нодах кластера и обеспечивают запуск сервисов на них.
  • master components - компоненты, необходимые для управления кластером (APIs, scheduler, etc).

Deployment в кластер kubernetes

Для деплоймента в кластер kubernetes нужно описать в docker-файлах сервисы Pod'а. Порядок такой:

  • создаем docker-файлик.
  • собираем контейнер
  • помещаем (push) контейнер в репозиторий контейнеров docker (cloud.docker.com)

В кластере kubernetes создаем namespace (логичекий контейнер для pod'ов, деплойментов (deployments) и т.д.)

kubectl create namespace _name_

Для деплоймента в kubernetes нужно описать три сущности:

  • Сам deployment, в котором описано какие docker-контейнеры нужно загрузить в pod, сколько таких pod'ов создать, а также какую конфигурацию применить к контейнерам (с помощью ConfigMap'ов).
  • ConfigMap'ы, которые содержат конфигурационные файлы, необходимые для работы приложений контейнерах. Например - конфигурация nginx для контейнера с nginx.
  • Service - способ доступа к приложениям в контейнерах (балансировка и проброс портов).

Ход обновления:

  • клонируем исходник из git.
  • собираем его.
  • собираем docker-контейнер с новым тегом версии, содержащий файлы приложения (docker build)
  • помещаем docker-контейнер в docker registry (docker push)
  • указываем в деплойменте тег новой версии контейнера.
  • применяем deployment (kubectl apply) в результате чего обновятся pod'ы.

Для автоматизации с помощью GitLab:

  • в свойствах проекта создаем Runner
  • по инструкци устанавливаем gitlab-runner и добавляем пользователя gitlab-runner в группу докера, для того, чтобы раннер мог запускать деплоймент.
  • после установки gitlab-runner его нужно зарегистрировать (gitlab-runner register)
  • В конфиге раннера прописываются стадии: build и deploy. build включает в себя создание докерконтейнера, пригодного для деплоя.

Доступ к private registry с паролем

У меня есть private registry на базе Nexus 3. Доступ туда осуществляется по логину-паролю (учетка из Active Directory).
Для того чтобы образы можно было забирать из частного запароленного репозитория нужно:

  • залогиниться в него:
docker login -u _username_ -p _password_ registry.domain.com
  • в результате в файлике ~/.docker/config.json окажутся учетные данные, необходимые для работы.
  • сделать из этого файлика секрет:
kubectl create secret generic registry-credentials -n namespace-name --from-file=.dockerconfigjson=<path_to_home>/.docker/config.json --type=kubernetes.io/dockerconfigjson
  • и в дальнейшем использовать этот секрет:
apiVersion: v1
kind: Pod
metadata:
  name: private-reg
  namespace: namespace-name
spec:
  containers:
  - name: private-reg-container
    image: <your-private-image>
  imagePullSecrets:
  - name: registry-credentials
  • Стоит обратить внимание на то, что секреты изолированы на уроне неймспейсов и в каджом неймспейсе должна быть копия секрета для доступа к registry.

Патчинг конфигурации из командной строки

https://stupefied-goodall-e282f7.netlify.com/contributors/devel/strategic-merge-patch/
http://jsonpatch.com/
Иногда нужно отредактировать объекты из скрипта, не запуская редактор.
Для этого у kubectl есть команда patch.

kubectl patch PersistentVolume elasticsearch-data --type=json -p='[{"op": "remove", "path": "/spec/claimRef"}]'
kubectl patch PersistentVolume my-pv-name-123465789 -p '{"spec":{"claimRef": null}}'
kubectl patch deployment es-master --type=json -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/resources"}]'
kubectl patch PersistentVolume elasticsearch-data  -p '{"spec":{"claimRef":{"$patch": "delete"}}}'
kubectl -n kubernetes-dashboard patch service kubernetes-dashboard  -p '{"spec":{"type":"NodePort"}}'

в данном случае - редактируем элемент номер 0 списка containers:

kubectl -n elasticsearch patch deployment es-master --type=json -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/cpu", "value":1}]'
kubectl -n elasticsearch patch deployment es-master --type=json -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/env/2", "value":{"name": "NUMBER_OF_MASTERS", "value": "1"}}]'
kubectl patch pod alpine-correct --type='json' -p='[{"op":"add","path":"/metadata/annotations", "value":{"testone.annotation.io":"test-one","testtwo.annotation.io":"test-two","testthree.annotation.io":"test-three"}}]'

В список:

kubectl -n elasticsearch patch sts es-data --type=json -p='[{"op": "add", "path": "/spec/template/spec/tolerations", "value":[{"effect":"NoSchedule","key":"node.kubernetes.io/rrr","operator":"Exists"}]}]'
kubectl patch ns default --type=json -p '[
{"op":"add","path":"/metadata/annotations","value":{"a":"1","b":"2"}},
{"op":"add","path":"/metadata/labels","value":{"c":"3"}}
]'
kubectl patch PersistentVolume elasticsearch-data --type=json -p='[{"op": "remove", "path": "/spec/claimRef"}]'

Найти все объекты kubernetes во всех неймспейсах

while read -r line; do echo "===============  $line  =================="; kubectl get $line -A; done < <(kubectl api-resources | grep -v NAME | cut -d ' ' -f1)

Удобное редактирование секретов kubernetes

https://github.com/lbolla/kube-secret-editor

sudo apt-get -y install python3-pip python-pip
sudo pip install PyYaml
sudo pip3 install PyYaml
sudo wget https://raw.githubusercontent.com/lbolla/kube-secret-editor/master/kube-secret-editor.py -O /usr/local/bin/kube-secret-editor.py
sudo chmod a+x /usr/local/bin/kube-secret-editor.py
alias kedit-secret="EDITOR=nano KUBE_EDITOR=/usr/local/bin/kube-secret-editor.py kubectl edit secret"
sudo awk -v line='alias kedit-secret="EDITOR=nano KUBE_EDITOR=/usr/local/bin/kube-secret-editor.py kubectl edit secret"' 'FNR==NR && line==$0{f=1; exit} END{if (!f) print line >> FILENAME}' /etc/bash.bashrc

После перелогина (или нового запуска/перезапуска) bash станет доступен alias kedit-secret и отредактировать секрет можно будет так:

kedit-secret -n nsname secret-name

Предоставление ограниченного доступа в кластер с помощью RBAC и сертификатов

https://medium.com/better-programming/k8s-tips-give-access-to-your-clusterwith-a-client-certificate-dfb3b71a76fe
Что нужно понимать.

  • В кубернетесе НЕ существует базы данных с Пользователями и Группами.
  • Аутентификация при доступе к кластеру осуществляется с помощью сертификатов. Фактически CN в сертификате - это имя субъекта (пользователя), обращающегося к кластеру. O в сертификате - это имя группы, в которую включе пользователь.
  • При использовании аутентификации по сертификатам нужно сгенерировать запрос на сертификат, на основании которого кластер может выпустить клиентский сертификат, с некоторым значением полей CN и O.
  • Привилегии раздаются с помощью ролей. Role - это набор привилегий для субъекта (пользователя или группы).
  • Для того, чтобы дать права владельцу сертификата нужно создать RoleBinding, которая свяжет роль с тем что написано в сертификате. Если даем права пользователю, то имя пользователя в RoleBinding должно совпадать со значением поля CN в сертификате. Если даем права группе, то имя группы в RoleBinding должно совпадать со значение поля O в сертификате.


Конкретная задача формулируется следующим образом:

  • Для обращений к api-серверу используется аутентификация по сертикатам.
  • Разработчику нужно предоставить доступ на просмотр объектов в заданном неймспейсе.
  • В перспективе аналогичные права понадобятся другим разработчикам. То есть - права будем выдавать на группу.
#!/bin/bash
set -e

KUB_CONTEXT='kub-apps-test'
KUB_USERNAME='developer'
KUB_USERGROUP='app-dev-full'
#cluster or ns (namespace)
#AUTH_SCOPE='cluster'
AUTH_SCOPE='ns'
# If AUTH_SCOPE = ns then we need namespace name
KUB_NAMESPACE='app-dev'
KUB_ROLE_NAME='app-dev-full'
# Comma separated quoted - '"get", "list"'. For all use "*"
KUB_ROLE_APIGROUPS='"*"'
KUB_ROLE_RESOURCES='"*"'
KUB_ROLE_VERBS='"*"'

echo "Switching to context '${KUB_CONTEXT}'..."
kubectl config use-context $KUB_CONTEXT

echo "Get Kubernetes Cluster Details..."
CLUSTER_NAME=${KUB_CONTEXT}-cluster
CLUSTER_ENDPOINT=`kubectl get po -n kube-system -o jsonpath="{.items[?(@.metadata.labels.component==\"kube-apiserver\")].metadata.annotations.kubeadm\.kubernetes\.io\/kube-apiserver\.advertise-address\.endpoint}" | cut -d' ' -f 1`
KUB_SYSTEM_TOKEN_SECRET=`kubectl get secret -n kube-system -o name | grep default-token | cut -d'/' -f 2`
CLUSTER_CA=`kubectl get secret -n kube-system $KUB_SYSTEM_TOKEN_SECRET -o jsonpath={".data.ca\.crt"}`

if [ ! -f ./${KUB_USERNAME}.key ]; then
    openssl genrsa -out ./${KUB_USERNAME}.key 4096
fi

echo "Create CSR config file ${KUB_USERNAME}.csr.cnf..."
cat <<EOF > ./${KUB_USERNAME}.csr.cnf
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[ dn ]
CN = ${KUB_USERNAME}
O = ${KUB_USERGROUP}

[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth
EOF

echo "Create CSR file ${KUB_USERNAME}.csr..."
openssl req -config ./${KUB_USERNAME}.csr.cnf -new -key ${KUB_USERNAME}.key -nodes -out ${KUB_USERNAME}.csr

echo "Create CertificateSigningRequest in ${KUB_CONTEXT} cluster..."
export BASE64_CSR=$(cat ./${KUB_USERNAME}.csr | base64 | tr -d '\n')
kubectl apply -f - <<EOF
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
  name: ${KUB_USERNAME}_csr
spec:
  groups:
  - system:authenticated
  request: ${BASE64_CSR}
  #signerName: kubernetes.io/kube-apiserver-client
  usages:
  - client auth
EOF

echo "Check if CSR created successfully..."
kubectl get csr ${KUB_USERNAME}_csr

echo "Approving CSR created..."
kubectl certificate approve ${KUB_USERNAME}_csr

echo "Download certificate for user ${KUB_USERNAME} to ${KUB_USERNAME}.crt file..."
kubectl get csr ${KUB_USERNAME}_csr -o jsonpath='{.status.certificate}' | base64 --decode > ${KUB_USERNAME}.crt

if [ "$AUTH_SCOPE" = "cluster" ]; then
echo "Create Cluster Role..."
kubectl apply -f - <<EOF
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
 name: ${KUB_ROLE_NAME}
rules:
- apiGroups: [${KUB_ROLE_APIGROUPS}]
  resources: [${KUB_ROLE_RESOURCES}]
  verbs: [${KUB_ROLE_VERBS}]
EOF

echo "Create Cluster Role Binding for group ${KUB_USERGROUP}..."
kubectl apply -f - <<EOF
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
 name: ${KUB_ROLE_NAME}-${KUB_USERGROUP}
subjects:
- kind: Group
  name: ${KUB_USERGROUP}
  apiGroup: rbac.authorization.k8s.io
roleRef:                             
 kind: ClusterRole                          
 name: ${KUB_ROLE_NAME}
 apiGroup: rbac.authorization.k8s.io 
EOF
fi

if [ "$AUTH_SCOPE" = "ns" ]; then
kubectl apply -f - <<EOF
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
 namespace: ${KUB_NAMESPACE}
 name: ${KUB_ROLE_NAME}
rules:
- apiGroups: [${KUB_ROLE_APIGROUPS}]
  resources: [${KUB_ROLE_RESOURCES}]
  verbs: [${KUB_ROLE_VERBS}]
EOF

kubectl apply -f - <<EOF
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
 name: ${KUB_ROLE_NAME}-${KUB_USERGROUP}
 namespace: ${KUB_NAMESPACE}
subjects:
- kind: Group
  name: ${KUB_USERGROUP}
  apiGroup: rbac.authorization.k8s.io
roleRef:                             
 kind: Role                          
 name: ${KUB_ROLE_NAME}
 apiGroup: rbac.authorization.k8s.io 
EOF
fi

CLIENT_CERTIFICATE_DATA=$(kubectl get csr ${KUB_USERNAME}_csr -o jsonpath='{.status.certificate}')
CLIENT_KEY_DATA=$(cat ./${KUB_USERNAME}.key | base64 |  tr -d '\n' )

cat <<EOF > ./kubeconfig_${KUB_USERNAME}_${CLUSTER_NAME}
apiVersion: v1
kind: Config
clusters:
- cluster:
    certificate-authority-data: ${CLUSTER_CA}
    server: https://${CLUSTER_ENDPOINT}
    #insecure-skip-tls-verify: true
  name: ${CLUSTER_NAME}
users:
- name: ${KUB_USERNAME}-${CLUSTER_NAME}
  user:
    client-certificate-data: ${CLIENT_CERTIFICATE_DATA}
    client-key-data: ${CLIENT_KEY_DATA}
contexts:
- context:
    cluster: ${CLUSTER_NAME}
    user: ${KUB_USERNAME}-${CLUSTER_NAME}
  name: ${KUB_USERNAME}-${CLUSTER_NAME}
current-context: ${KUB_USERNAME}-${CLUSTER_NAME}
EOF

Генерируем закрытый ключ:

openssl genrsa -out mike.key 4096

Для генерации закрытого ключа пользователя нужно подготовить файлик mike.csr.cnf, в котором указать имя пользователя и группу, которой мы дадим права:

[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[ dn ]
CN = mike
O = development

[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth

Тут:

  • CN - имя пользователя
  • O - имя группы

И дальше генерируем запрос сертификата:

openssl req -config ./mike.csr.cnf -new -key mike.key -nodes -out mike.csr

В итоге - получаем файл запроса сертификата - mike.csr.

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

export BASE64_CSR=$(cat ./mike.csr | base64 | tr -d '\n')

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

kubectl apply -f - <<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: csr
spec:
  groups:
  - system:authenticated
  request: ${BASE64_CSR}
  usages:
  - digital signature
  - key encipherment
  - server auth
  - client auth
EOF

Убеждаемся, что запрос создан:

$ kubectl get csr
NAME      AGE   REQUESTOR          CONDITION
csr       31s   kubernetes-admin   Pending

И выпускаем сертификат:

kubectl certificate approve csr

Если при аппруве появляется ошибка типа:

error: no kind "CertificateSigningRequest" is registered for version "certificates.k8s.io/v1" in scheme "k8s.io/kubectl/pkg/scheme/scheme.go:28"

то скорее всего делл в том, что в кластере несколько версий API для ресурса certifictes, либо версия kubectl отстает от версии кластера. Проверяем это:

kubectl get apiservice | grep certificates
kubectl version --short

Если там две версии API для ресурса certifictes, то удаляем старую, а также обновляем kubectl при необходимости:

kubectl delete apiservice v1beta1.certificates.k8s.io

А теперь - извлекаем его:

kubectl get csr csr -o jsonpath='{.status.certificate}' | base64 --decode > mike.crt
kubectl apply -f - <<EOF
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
 namespace: development
 name: dev
rules:
- apiGroups: [""]
  resources: ["pods", "services"]
  verbs: ["get", "list"]
EOF

Привязываем пользователя к роли (по имени):

kubectl apply -f - <<EOF
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
 name: dev
 namespace: development
subjects:
- kind: User
  name: mike
  apiGroup: rbac.authorization.k8s.io
roleRef:
 kind: Role
 name: dev
 apiGroup: rbac.authorization.k8s.io
EOF

Либо к группе:

kubectl apply -f - <<EOF
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
 name: dev
 namespace: development
subjects:
- kind: Group
  name: development
  apiGroup: rbac.authorization.k8s.io
roleRef:                             
 kind: Role                          
 name: dev
 apiGroup: rbac.authorization.k8s.io 
EOF

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

export USER="mike"
export CLUSTER_NAME=$(kubectl config view --minify -o jsonpath={.clusters[0].name})
export CLUSTER_ENDPOINT=$(kubectl config view --raw -o json | jq -r ".clusters[] | select(.name == \"$CLUSTER_NAME\") | .cluster.server")
export CLIENT_CERTIFICATE_DATA=$(kubectl get csr mycsr -o jsonpath='{.status.certificate}')
export CLIENT_KEY_DATA=$(cat ./mike.key | base64 | tr -d '\n')
export CLUSTER_CA=$(kubectl get secret -o jsonpath="{.items[?(@.type==\"kubernetes.io/service-account-token\")].data['ca\.crt']}")

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

cat <<EOF > kubeconfig
apiVersion: v1
kind: Config
clusters:
- cluster:
    certificate-authority-data: ${CLUSTER_CA}
    server: ${CLUSTER_ENDPOINT}
    #insecure-skip-tls-verify: true
  name: ${CLUSTER_NAME}
users:
- name: ${USER}
  user:
    client-certificate-data: ${CLIENT_CERTIFICATE_DATA}
    client-key-data: ${CLIENT_KEY_DATA}
contexts:
- context:
    cluster: ${CLUSTER_NAME}
    user: ${USER}
  name: ${USER}-${CLUSTER_NAME}
current-context: ${USER}-${CLUSTER_NAME}
EOF

В чарте в директории files я имею просто скрипт, который должне запуститься в init-контейнере.
Я поменщаю этот скрипт в секрет с помощью такого манифеста:

apiVersion: v1
kind: Secret
metadata:
  name: files
type: Opaque
data: 
{{ (.Files.Glob "files/*").AsSecrets | indent 2 }}

В деплойменте я запускаю init-контейнер, в котором монтируется секрет с этим скриптом и выполняется этот скрипт:

...
      initContainers:
      - name: copy-files
        image: docker.rdleas.ru/busybox
        command:
        - "sh"
        - "-c"
        - "cp /tmp/files/init-script.sh /tmp/ && chmod u+x /tmp/init-script.sh && /tmp/init-script.sh"
        volumeMounts:
        - name: files
          mountPath: /tmp/files/
...
      volumes:
      - name: files
        secret:
          secretName: files

то есть мне надо сделать скрипт исполняемым (дать соотвествующие разрешения), но смонтирован он как read-only, поэтому я предварительно копирую его. А после того, как разрешено его исполнение - запускаю скрипт.

Обновление сертификатов

https://github.com/kubernetes/kubeadm/issues/581#issuecomment-471575078
Обновление сертификатов (даже просроченных) на мастерах.

sudo kubeadm alpha certs renew apiserver
sudo kubeadm alpha certs renew apiserver-kubelet-client
sudo kubeadm alpha certs renew front-proxy-client

На ноде не стартует kubelet, при старте в /var/log/syslog такое:

Nov 22 15:10:08 kub-master01 systemd[1]: Started Kubernetes systemd probe.
Nov 22 15:10:08 kub-master01 kubelet[6927]: I1122 15:10:08.032987    6927 server.go:411] Version: v1.19.3
Nov 22 15:10:08 kub-master01 kubelet[6927]: I1122 15:10:08.033277    6927 server.go:831] Client rotation is on, will bootstrap in background
Nov 22 15:10:08 kub-master01 kubelet[6927]: E1122 15:10:08.035302    6927 bootstrap.go:265] part of the existing bootstrap client certificate is expired: 2020-11-20 08:19:12 +0000 UTC
Nov 22 15:10:08 kub-master01 kubelet[6927]: F1122 15:10:08.035329    6927 server.go:265] failed to run Kubelet: unable to load bootstrap kubeconfig: stat /etc/kubernetes/bootstrap-kubelet.conf: no such file or directory

Проблема усугубляется тем, что это однонодовый кластер и просто сделать kubeadm join не выйдет.

Вот такой конвейер позвоялет увидеть дату окончания сертификата ноды, который хранится в kubelet.conf.

cat /etc/kubernetes/kubelet.conf  | grep 'client-certificate-data:' | cut -d':' -f2 | sed 's/\s*//g' | base64 -d | openssl x509 -noout -enddate
notAfter=Nov 20 08:19:12 2020 GMT

Извлекаем сертификат:

cat /etc/kubernetes/kubelet.conf  | grep 'client-certificate-data:' | cut -d':' -f2 | sed 's/\s*//g' | base64 -d > ./node.crt.pem

Извлекаем ключ:

cat /etc/kubernetes/kubelet.conf  | grep 'client-key-data:' | cut -d':' -f2 | sed 's/\s*//g' | base64 -d > ./node.key.pem

Генерируем запрос:

openssl x509 -x509toreq -signkey ./node.key.pem -in ./node.crt.pem -out ./node.csr 

Проверяем запрос:

openssl req -text -noout -verify -in ./node.csr

Создаем новый сертификат

openssl x509 -req -in node.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -set_serial 10 -out ./new_node.crt.pem

Кодируем новый сертификат в base64

base64 -w 0 ./new_node.crt.pem

И затем подставляем в поле client-certificate-data: в файлике /etc/kubernetes/kubelet.conf

Kubernetes LDAP Auth

LDAP-аутентификацию к кластеру kubernetes можно прикрутить с помощью различных средств, но все они используют один подход и схожие по назначению компоненты:

  • Служба каталога с поддрежкой протокола LDAP. Например - Active Directory
  • Провайдер OpenID Connect, с которым взаимодействует кластер kubernetes. OIDC - это приложение, которое аутентифицирует пользователей в LDAP и сообщает кластеру Kubernetes сведения о нем, чтобы kubernetes мог сгенерировать сертификат.
  • Некое web-приложение, с которым взаимодействует пользователи. Оно перенапрявляет пользователя на OpenID Connector, получает от кластера kubernetes сертификат и генерирует конфиг для kubectl.
  • Dex
  • Keycloak

Мне понравился такой вариант - Keycloak в качестве OIDC-провайдера и kubelogin в качестве способа получения токена.

В параметры клиента в keycloak в valid redirect uris нужно прописать http://localhost:8000 (или *)

На каждой мастер-ноде редактируем файл /etc/kubernetes/manifests/kube-apiserver.yaml и добавляем туда такие параметры:

    - --oidc-ca-file=/etc/ssl/certs/RDLeas_Root_CARDleas-SRV-DC02-CA.pem
    - --oidc-client-id=kubernetes
    - --oidc-groups-claim=groups
    - --oidc-issuer-url=https://sso.rdleas.ru/auth/realms/rdleas
    - --oidc-username-claim=email

Редактируем Configmap и также добавляем туда эти параметры:

kubectl edit cm -n kube-system kubeadm-config
data:
  ClusterConfiguration: |
    apiServer:
      ...
      extraArgs:
        authorization-mode: Node,RBAC
        oidc-ca-file: /etc/ssl/certs/RDLeas_Root_CARDleas-SRV-DC02-CA.pem
        oidc-client-id: kubernetes          
        oidc-groups-claim: groups
        oidc-issuer-url: https://sso.rdleas.ru/auth/realms/rdleas
        oidc-username-claim: email

Устанавливаем плагин kubelogin.
Скачиваем бинарник https://github.com/int128/kubelogin/releases
Извлекаем его куда-то в $PATH, например - /usr/local/bin/kubelogin

В ~/.kube/config прописываем пользователя:

- name: oidc
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      args:
      - get-token
      - --oidc-issuer-url=https://sso.rdleas.ru/auth/realms/rdleas
      - --oidc-client-id=kubernetes
      command: kubelogin
      env: null
      provideClusterInfo: false

И контекст с его участием:

- context:
    cluster: sbl-apps-dev
    user: oidc
  name: sbl-apps-dev-oidc

Название групп в манифестах должно быть точно таким же как и в токене. В моей инсталляции в начале названия группы есть слеш.
Права на неймспейс (в даннос лучае - default) выдаем так:

NAMESPACE=default
GROUP='/Kubernetes_Default_Ns_Admins'
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: ${NAMESPACE}-admins
  namespace: ${NAMESPACE}
rules:
- apiGroups:
  - '*'
  resources:
  - '*'
  verbs:
  - '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: kubernetes-${NAMESPACE}-namespace-admins
  namespace: ${NAMESPACE}
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: ${NAMESPACE}-admins
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: ${GROUP}
EOF

Права на весь кластер выдаем так:

GROUP='/Kubernetes_Cluster_Admins'
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-admins-oidc
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: ${GROUP}
EOF

Поглядеть текущий токен можно так:

kubelogin get-token --oidc-issuer-url=https://sso.rdleas.ru/auth/realms/rdleas --oidc-client-id=kubernetes

Расшифровать его (и увидеть список групп в нем) можно тут: https://jwt.io/

invalid bearer token, oidc: email not verified

В логе kube-apiserver ошибка

Unable to authenticate the request due to an error: [invalid bearer token, oidc: email not verified]

В токене такое:

"email_verified": false

Нужно выключить верификацию email на keycloak (поле email_verified не будет появляться в токене).
В Keycloak идем: Client ScopesEmailMappers и удаляем Email verified.

Поглядеть подробный лог можно так:

kubectl --token=$token --server=https://192.168.1.2:6443 --insecure-skip-tls-verify get po --v=10

Ссылки

DevOps

Мониторинг веб-приложения - graphana

Enter your comment. Wiki syntax is allowed:
 
  • devops/kubernetes.1616418214.txt
  • Last modified: 2021/03/22 13:03
  • by admin