https://ansilh.com/01-introduction/ 
https://ansilh.com/15-k8s_from_sratch/ 
Материалов про kubernetes очень много. В процессе обучения просто я фиксирую для себя то что считаю важным. Это не тутораил и не гайд. Это просто мои заметки.
https://kubernetes.io/docs/reference/kubectl/cheatsheet/
Создаем файл 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>
kubectl run kubia --image=luksa/kubia --port=8080 --generator=run/v1
kubectl run создаст все необходимые компоненты без необходимости декларировать компоненты с помощью JSON или YAML. 
–generator=run/v1 - нужен для того, чтобы текущая версия kubernetes не создавала Deployment, а создала ReplicationController. 
В результате будет создан Pod с одним контейнером kubia. 
При выполнении команды произошло следующее:
Список 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). 
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
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 можно разделить на две части:
Для деплоймента в кластер kubernetes нужно описать в docker-файлах сервисы Pod'а. Порядок такой:
В кластере kubernetes создаем namespace (логичекий контейнер для pod'ов, деплойментов (deployments) и т.д.)
kubectl create namespace _name_
Для деплоймента в kubernetes нужно описать три сущности:
Ход обновления:
Для автоматизации с помощью GitLab:
У меня есть private registry на базе Nexus 3. Доступ туда осуществляется по логину-паролю (учетка из Active Directory). 
Для того чтобы образы можно было забирать из частного запароленного репозитория нужно:
docker login -u _username_ -p _password_ registry.domain.com
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
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"}]}]'
Добавляем порт для контейнера в Deployment:
kubectl patch deployment mcs-api --type=json -p='[{"op": "add", "path": "/spec/template/spec/containers/0/ports", "value":[{"containerPort": 5000}]}]'
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"}]'
while read -r line; do echo "=============== $line =================="; kubectl get $line -A; done < <(kubectl api-resources | grep -v NAME | cut -d ' ' -f1)
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
https://medium.com/better-programming/k8s-tips-give-access-to-your-clusterwith-a-client-certificate-dfb3b71a76fe 
Что нужно понимать.
Конкретная задача формулируется следующим образом:
#!/bin/bash
set -e
KUB_CONTEXT='kubernetes-admin@kubernetes'
KUB_USERNAME='developer-ro'
KUB_USERGROUP='mcs-ro'
#cluster or ns (namespace)
#AUTH_SCOPE='cluster'
AUTH_SCOPE='ns'
# If AUTH_SCOPE = ns then we need namespace name
KUB_NAMESPACE='default'
KUB_ROLE_NAME="${KUB_USERGROUP}-role"
# Comma separated quoted - '"get", "list"'. For all use "*"
KUB_ROLE_APIGROUPS='"*"'
KUB_ROLE_RESOURCES='"*"'
#KUB_ROLE_VERBS='"get", "list"'
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`
[ -z "$CLUSTER_ENDPOINT" ] && CLUSTER_ENDPOINT=`kubectl cluster-info | grep 'control plane' | awk '{print $NF}' | sed 's/^.*\/\///' | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g"`
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
kubectl delete certificatesigningrequests ${KUB_USERNAME}_csr
Генерируем закрытый ключ:
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
Тут:
И дальше генерируем запрос сертификата:
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
#!/bin/bash
set -e
KUB_CONTEXT='anima-lightning2-dev'
KUB_USERNAME='dashboard-ro'
KUB_NAMESPACES=('lightning-dev' 'lightning-tst')
######KUB_USERGROUP='mcs-ro'
#cluster or ns (namespace)
#AUTH_SCOPE='cluster'
AUTH_SCOPE='ns'
# If AUTH_SCOPE = ns then we need namespace name
#KUB_NAMESPACES=('default')
# Comma separated quoted - '"get", "list"'. For all use "*"
KUB_ROLE_APIGROUPS='"*"'
KUB_ROLE_RESOURCES='"*"'
#KUB_ROLE_VERBS='"get", "list"'
KUB_ROLE_VERBS='"get", "list"'
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`
[ -z "$CLUSTER_ENDPOINT" ] && CLUSTER_ENDPOINT=`kubectl cluster-info | grep 'control plane' | awk '{print $NF}' | sed 's/^.*\/\///' | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g"`
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"}`
kubectl create serviceaccount ${KUB_USERNAME}-sa -n kube-system || true
echo "Create ClusterRole '${KUB_USERNAME}-ns-cluster-role' to List Namespaces..."
kubectl apply -f - <<EOF
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: ${KUB_USERNAME}-ns-cluster-role
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["get", "list"]
EOF
echo "Create Cluster Role Binding for group '${KUB_USERNAME}-ns-cluster-role' to List Namespaces..."
kubectl apply -f - <<EOF
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: ${KUB_USERNAME}-ns-cluster-rolebinding
subjects:
- kind: ServiceAccount
  name: ${KUB_USERNAME}-sa
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: ${KUB_USERNAME}-ns-cluster-role
  apiGroup: rbac.authorization.k8s.io
EOF
if [ "$AUTH_SCOPE" = "ns" ]; then
for KUB_NAMESPACE in ${KUB_NAMESPACES[@]}; do
kubectl create ns ${KUB_NAMESPACE} || true
echo "Creating Role - '${KUB_USERNAME}-role' in the namespace '${KUB_NAMESPACE}'..."
kubectl replace -f - <<EOF
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: ${KUB_NAMESPACE}
  name: ${KUB_USERNAME}-role
rules:
- apiGroups: [${KUB_ROLE_APIGROUPS}]
  resources: [${KUB_ROLE_RESOURCES}]
  verbs: [${KUB_ROLE_VERBS}]
EOF
echo "Creating RoleBinding for the '${KUB_USERNAME}-role' in the namespace '${KUB_NAMESPACE}'..."
kubectl replace -f - <<EOF
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: ${KUB_USERNAME}
  namespace: ${KUB_NAMESPACE}
subjects:
- kind: ServiceAccount
  name: ${KUB_USERNAME}-sa
  namespace: kube-system
roleRef:
  kind: Role
  name: ${KUB_USERNAME}-role
  apiGroup: rbac.authorization.k8s.io
EOF
done
fi
if [ "$AUTH_SCOPE" = "cluster" ]; then
echo "Creating ClusterRole '${KUB_USERNAME}-cluster-role' to access resources..."
kubectl apply -f - <<EOF
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: ${KUB_USERNAME}-cluster-role
rules:
- apiGroups: [${KUB_ROLE_APIGROUPS}]
  resources: [${KUB_ROLE_RESOURCES}]
  verbs: [${KUB_ROLE_VERBS}]
EOF
echo "Create Cluster Role Binding for group '${KUB_USERNAME}-cluster-role' to access resources..."
kubectl apply -f - <<EOF
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: ${KUB_USERNAME}-cluster-rolebinding
subjects:
- kind: ServiceAccount
  name: ${KUB_USERNAME}-sa
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: ${KUB_USERNAME}-cluster-role
  apiGroup: rbac.authorization.k8s.io
fi
EOF
fi
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: ${KUB_USERNAME}-sa-token
  namespace: kube-system
  annotations:
    kubernetes.io/service-account.name: ${KUB_USERNAME}-sa
type: kubernetes.io/service-account-token
EOF
SA_TOKEN=`echo "kubectl get secrets -n kube-system -o jsonpath=\"{.items[?(@.metadata.annotations['kubernetes\\.io/service-account\\.name']=='${KUB_USERNAME}-sa')].data.token}\" | base64 --decode" | /bin/bash`
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:
    token: ${SA_TOKEN}
contexts:
- context:
    cluster: ${CLUSTER_NAME}
    user: ${KUB_USERNAME}-${CLUSTER_NAME}
  name: ${KUB_USERNAME}-${CLUSTER_NAME}
current-context: ${KUB_USERNAME}-${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 
Обновление сертификатов (даже просроченных) на мастерах. 
Проверить валидность:
kubeadm certs check-expiration
Обновить все сразу:
kubeadm certs renew all
Обновить по одному:
kubeadm certs renew apiserver kubeadm certs renew apiserver-kubelet-client kubeadm certs renew front-proxy-client kubeadm certs renew admin.conf
На ноде не стартует 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
LDAP-аутентификацию к кластеру kubernetes можно прикрутить с помощью различных средств, но все они используют один подход и схожие по назначению компоненты:
Мне понравился такой вариант - 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/ 
В логе 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 Scopes → Email → Mappers и удаляем Email verified.
Поглядеть подробный лог можно так:
kubectl --token=$token --server=https://192.168.1.2:6443 --insecure-skip-tls-verify get po --v=10
https://uni.dtln.ru/digest/autentifikaciya-v-kubernetes-s-pomoshchyu-dex-prikruchivaem-ldap 
https://habr.com/ru/post/436238/ 
https://habr.com/ru/company/dataline/blog/497482/ 
https://github.com/mintel/dex-k8s-authenticator 
Если так случайно вышло, что вы удалили PersistentVolume, то не отчаивайтесь! Он будет находитьтся в состоянии Terminating до тех пор, пока существуют pod, куда он смонтирован и PersistentVolumeClaim, которая породила этот PV и удаление можно отменить. 
На помощь приходит специальная утилитка: https://github.com/jianz/k8s-reset-terminating-pv
git clone https://github.com/jianz/k8s-reset-terminating-pv.git cd k8s-reset-terminating-pv go build -o resetpv
Собранный бинарник: resetpv.tar.gz 
Заходим на мастер-ноду, архивируем сертификаты и смотрим какой сертификат за что отвечает:
ssh user@master-node sudo -H tar -cvzf ~/etcd-pki.tar.gz /etc/kubernetes/pki/etcd sudo docker ps --no-trunc | grep etcd exit
В выводе среди всего прочего будет такое:
--advertise-client-urls=https://10.77.68.1:2379 --cert-file=/etc/kubernetes/pki/etcd/server.crt --key-file=/etc/kubernetes/pki/etcd/server.key --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
В вашей инсталляции имена, пути и порты могут быть иными. 
И забираем с мастер-ноды кластера архив с сертификатами, которые необходимы для подключения к etcd кластера:
scp user@master-node:/home/user/etcd-pki.tar.gz ./ tar -xvf ./etcd-pki.tar.gя mv ./etc/kubernetes/pki/etcd/* ./
И дальше применяем утилиту:
./resetpv --etcd-ca ./ca.crt --etcd-cert ./server.crt --etcd-key ./server.key --etcd-host 10.77.68.1 --etcd-port 2379 pv-name
Если вдруг так вышло, что вы удалили все PV в кластере (как я - хотел удалить pvc в неймспейсе но просто опечатался и вместо pvc ввел pv):
kubectl delete -n namespace pv --all
то чтобы отменить удаление всех PV делаем так:
PVs=`kubectl get pv | grep Terminating | awk '{print $1}' | grep -v NAME`
for pv in $PVs; do ./resetpv --etcd-ca ./ca.crt --etcd-cert ./server.crt --etcd-key ./server.key --etcd-host 10.77.68.1 --etcd-port 2379 $pv; done
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-service
  labels:
    app: my-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-service
  template:
    metadata:
      labels:
        app: my-service
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: my-service
Либо:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - {{ .Release.Name }}-logstash
            topologyKey: kubernetes.io/hostname
Разница между этими подходами описана тут: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/#comparison-with-podaffinity-podantiaffinity 
В двух словах - podAntiAffinity более старый и простой способ и в будущем - предпочтительно пользоваться topologySpreadConstraints, который поддерживает не только распределение по нодам, но и по зонам доступности в облаках.  
И другие вопросы про то как давать доступ к индивидуальным подам реплик стейтфуллсетов. 
https://www.tigera.io/blog/exposing-statefulsets-in-kubernetes/ 
Обычно сервисы используются в k8s для балансировки запросов на все реплики микросервиса, однако, в слуаче со statefull-приложениями может понадобиться доступ к конкретной реплике. Для этого в k8s существуют Statefullset'ы и связанные с ними Headless-сервисы. 
Что такое Statefullset - это способ создать набор именованных реплик сервиса, каждая из которых будет сохранять свое имя и после перезапуска. 
Что такое Headless Service - это сервис, который НЕ имеет адреса в кластере (ClusterIP: None) и НЕ выполняет балансировку подключний по эндпоинтам, а просто является списком эндпоинтов для именованных реплик, управляемых Statefullset'ом. А для доступа к именованным репликам в DNS кластера генерируются имена:
<StatefulSet>-<Ordinal>.<Service>.<Namespace>.svc.cluster.local
например:
app-0.myapp.default.svc.cluster.local.
Вопрос - а как же можно опубликовать именованную реплику в составе Statefullset'а, например через Ingress?? Ведь для этого надо как-то сослаться на сервис, который будет связан с портом на конкретной реплике! 
Все просто - каждый под, управляеый Statefullset'ом имеет уникальну метку (примерно такую - statefulset.kubernetes.io/pod-name: app-0 ), которую можно использовать в качестве селектора в сервисе:
apiVersion: v1
kind: Service
metadata:
  name: app-0
spec:
  type: LoadBalancer
  selector:
    statefulset.kubernetes.io/pod-name: app-0
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
kubectl get secret my-tlssecret --namespace=nginx-ns -o yaml | sed 's/namespace: .*/namespace: default/' | kubectl apply -f -
Список существующих в кластере Сustom Resource Definitions
kubectl get customresourcedefinitions
или
kubectl get crd
ну и посмотреть спецификацию:
kubectl describe crd <crd_name>
В общем случае плавно рестартить можно так:
kubectl rollout restart deployment my-app
А для всех деплойментов в неймспейсе:
ns=namespacename; for deployment in `kubectl get deployment -n ${ns} | grep -v NAME | awk '{print $1}'`; do kubectl -n ${ns} rollout restart deployment ${deployment}; done
Плавный рестарт всех подиков во всех деплойментах во всех неймспейсах:
for ns in `kubectl get ns | grep -v NAME | awk '{print $1}'`; do for deployment in `kubectl get deployment -n ${ns} | grep -v NAME | awk '{print $1}'`; do kubectl -n ${ns} rollout restart deployment ${deployment}; done; done 
При старте подика формируется файл /etc/resolve.conf в котором прописано как будут резолвиться имена. 
Важным параметром является options ndots:5 
Этот параметр задает то, сколько МИНИМУМ точек должно быть точек в имени, чтобы рассмастривать его как FQDN и не пытаться искать это имя с суффиксами из search. 
Изменить его значение можно с помощью специального раздела конфигурации подика:
apiVersion: v1
kind: Pod
metadata:
  namespace: default
  name: dns-example
spec:
  containers:
    - name: test
      image: nginx
  dnsConfig:
    options:
      - name: ndots
        value: "1"