Operations
Deploying & configuring complex systems.
- Alpine Linux
Alpine OS Config
- Ansible
Ansible installation, config & usage
- Grafana Loki
Grafana Loki installation, config & usage
- Kubernetes
Kubernetes Administration
Deploying & configuring complex systems.
Alpine OS Config
Ansible installation, config & usage
Grafana Loki installation, config & usage
Kubernetes Administration
apk add --update open-vm-tools
rc-service open-vm-tools start
rc-update add open-vm-tools
External Link In file: /etc/network/interfaces
DHCP: iface eth0 inet dhcp
STATIC:
iface eth0 inet static
address 192.168.1.150/24
gateway 192.168.1.1
In file: /etc/resolv.conf
nameserver 8.8.8.8
nameserver 8.8.4.4
External Link
rc-update add cgroups
Bugfix:
echo "cgroup /sys/fs/cgroup cgroup defaults 0 0" >> /etc/fstab
cat > /etc/cgconfig.conf <<END
mount {
cpuacct = /cgroup/cpuacct;
memory = /cgroup/memory;
devices = /cgroup/devices;
freezer = /cgroup/freezer;
net_cls = /cgroup/net_cls;
blkio = /cgroup/blkio;
cpuset = /cgroup/cpuset;
cpu = /cgroup/cpu;
}
END
sed -i '/^default_kernel_opts/s/=.*$/="quiet rootfstype=ext4 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory"/' /etc/update-extlinux.conf
update-extlinux
apk add --no-cache iptables curl
Do not install Ansible from local OS repo - it is usually older in version there. Use latest Python pip repo to install a fresh version.
sudo apt install python3-distutils
wget http://bootstrap.pypa.io/get-pip.py
sudo python3 get-pip.py
sudo pip3 install ansible
ansible --version
Ansible will be the latest version supporting current Python in OS. So to get latest Ansible , Python must be updated as well!
Do not update the default Python in OS - it is used by system services, which may break!
Build Python:
sudo apt install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev curl libbz2-dev
wget https://www.python.org/ftp/python/3.9.15/Python-3.9.15.tgz
tar -xf Python-3.9.15.tgz
cd Python-3.9.15
./configure --enable-optimizations
make -j 8
Installing Python:
sudo make altinstall # now Python 3.9x is installed on separate path, while Python 3.7 in OS is unchanged
sudo python3.9 -m pip install --upgrade pip
sudo python3.9 -m pip install ansible
ansible --version # now ansible is the latest version
---
- import_playbook: playbooks/db.yml
- import_playbook: playbooks/front.yml
- import_playbook: playbooks/zoneminder.yml
...
front.yml - role example:
---
- name: front-end play
hosts: all
gather_facts: yes
become: yes
tasks:
- name: include role apache
include_role:
name: apache
- name: include role php
include_role:
name: php
...
apache / tasks / main.yml - tasks example:
---
- name: install apache
apt:
name: apache2
- name: Enable service apache2
ansible.builtin.systemd:
name: apache2
enabled: yes
masked: no
- name: Make sure apache2 is running
ansible.builtin.systemd:
state: started
name: apache2
...
Directories inside a role:
- defaults - variable values by default
-- main.yml
- vars - variables defined by role (for other roles)
-- main.yml
- tasks - jobs to be completed
-- main.yml
- handlers - actions to be taken after checks
-- main.yml
- files - static files to be copied into client machine
- templates
Create inventory file with IP addresses. Check computers in inventory by doing a ping:
ansible all -i inventory -u vagrant -m ping -k
# -i - <inventory file>
# -u - <username>
# -m - <select module>
# -k - <interactive password prompt>
ansible all -i inventory -u admin -m ping -k -vvv # debug mode with verbosity 3
[db]
vm ansible_host=192.168.1.98 ansible_user=aagern
[app]
vm ansible_host=192.168.1.98 ansible_user=aagern
[front]
vm ansible_host=192.168.1.98 ansible_user=aagern
[all:vars]
ansible_ssh_extra_args="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
ansible_python_interpreter=/usr/bin/python3
web1 ansible_ssh_host=192.168.33.20
db1 ansible_ssh_host=192.168.33.30
[webservers]
web1
[dbservers]
db1
[datacenter:children]
webservers
dbservers
[datacenter:vars]
ansible_ssh_user=vagrant
ansible_ssh_pass=vagrant
Order of precedence for the config file:
First config file found wins, Ansible stops looking for other config files.
Specify: $ANSIBLE_
Override settings on the fly:
$ export ANSIBLE_FORKS=10
[defaults] forks - how many parallel processes does Ansible handle. Default=5, production recommended=20
host_key_checking - check host key before sending commands. Default=True (for production), for dev/test systems =False (easy control)
log_path - Ansible logging. Default=Null, produstion recommended - set path all Ansible users can write to.
Patterns to choose hosts/groups:
Example:
ansible webservers:dbservers -i inventory -m service -a "name=iptables state=stopped" --sudo
Complex pattern example: webservers:&production:!python3 # apply to web servers in production but not in the Python3 group
ansible-doc -l # all installed modules list
ansible-doc <module-name> # module man
ansible-doc -s <module-name> # playbook snippets code examples on module
ansible -i inventory web1 -m setup # gather all available system info
ansible -i inventory web1 -m setup -a "filter=ansible_eth*" # gather info on NICs
ansible -i inventory all -m setup --tree ./setup # form an inventory of files in /setup/ folder with info on targeted systems
ansible webservers -i inventory -m yum -a "name=httpd state=present" -u vagrant --sudo
# name - name of package (Apache)
# present - if package is not there, install. If it is there - do nothing and report "changed: false" (idempotence test)
ansible webservers -i inventory -m service -a "name=httpd state=started enabled=yes" -u vagrant --sudo
# name - name of service (Apache)
# state = started/stopped - make idempotence test and change if necessary
# enabled = yes/no - autostart on system boot
# Tasks in a playbook are executed top down. Tasks use modules.
tasks:
- name: Name the task for readability
module: parameters=go_here
# Example:
- name: Deploy Apache Configuration File
copy: src=../ansible/files/configuration/httpd.conf
dest=/etc/httpd/conf/
Playbook execution: ansible-playbook my_playbook.yml
Playbook example:
---
# -------- Global play declaration
- hosts: webservers
## ----- Variables per play
vars:
git_repo: https://github.com/repo.git
http_port: 8081
db_name: wordpress
## ------------------------
### ---- Declare user to run tasks
sudo: yes
sudo_user: wordpress_user
### ------------------------------
gather_facts: no # dont't gather facts with SETUP module (default gathers facts - expensive in time)
remote_user: root
tasks:
# --------------------------------
- name: Install Apache
yum: name=httpd state=present
- name: Start Apache
service: name=httpd state=started
Use “- include” and “- include_vars” directives to include playbook files:
tasks:
- include: wordpress.yaml
vars:
sitename: My Awesome Site
- include: reverse_proxy.yaml
- include_vars: variables.yaml
Use the output of one task for another task:
tasks:
- shell: /usr/bin/whoami
register: username
- file: path=/home/myfile.txt
owner: {{ username }}
Add screen output and print content of variables:
tasks:
- debug: msg="This host is {{ inventory_hostname }} during execution"
- shell: /usr/bin/whoami
register: username
- debug: var=username
Promt user during execution:
- hosts: web1
vars_prompt:
- name: "sitename"
prompt: "What is the new site name?"
tasks:
- debug: var=username
A handler can be informed to execute a task (restart service) only if state=changed.
Handlers syntax is the same as tasks syntax:
tasks:
- name: Copy Apache Config files
- copy: src=../files/httpd.conf
dest=/etc/httpd/config/
notify:
- Apache Restart
handlers:
- name: Apache Restart
service: name=httpd state=restarted
Commence a task if condition is True:
tasks:
- name: Install Httpd Package
apt: name=httpd state=present
when: ansible_os_family == "RedHat"
- name: Install Apache2 Package
yum: name=apache2 state=present
when: ansible_os_family == "Debian"
Check output of previous task as condition to run next task:
tasks:
- name: Stop iptables now
service: name=iptables state=stopped
register: result
ignore_errors: yes # supress default stop on error
- debug: msg="Failure!"
when: result|failed # Debug message will only be shown if task has failed
# other conditions are "result|success", "result|skipped"
Bring variable check to BOOL check:
- name: "test"
hosts: local
vars_prompt:
- name: "os_type"
prompt: "What OS? (centos or ubuntu)"
default: "centos"
private: no
vars:
- is_ubuntu: "{{os_type == 'ubuntu'}}"
- is_debian: "{{os_type == 'debian'}}"
tasks:
- debug: msg="this shows the conditional if variable equals ubuntu"
when: is_ubuntu|bool
- debug: msg="this shows the conditional if variable equals centos"
when: is_centos|bool
Create dirs for Loki:
cd ~
mkdir promtail
mkdir loki
mkdir grafana
vim docker-compose.yml
version: "3"
networks:
loki:
services:
loki:
image: grafana/loki:2.4.0
volumes:
- /home/aagern/loki:/etc/loki
ports:
- "3101:3100"
restart: unless-stopped
command: -config.file=/etc/loki/loki-local-config.yaml
networks:
- loki
promtail:
image: grafana/promtail:2.4.0
volumes:
- /var/log:/var/log
- /home/aagern/promtail:/etc/promtail
restart: unless-stopped
command: -config.file=/etc/loki/promtail-local-config.yaml
networks:
- loki
grafana:
image: grafana/grafana:latest
user: "1000"
volumes:
- /home/aagern/grafana:/var/lib/grafana
ports:
- "3000:3000"
restart: unless-stopped
networks:
- loki
The config files MUST be in the inner container directory!
wget https://raw.githubusercontent.com/grafana/loki/master/cmd/loki/loki-local-config.yaml
wget https://raw.githubusercontent.com/grafana/loki/main/clients/cmd/promtail/promtail-local-config.yaml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3101/loki/api/v1/push
# Local machine logs
scrape_configs:
- job_name: local
static_configs:
- targets:
- localhost
labels:
job: varlogs
__path__: /var/log/*log
#scrape_configs:
#- job_name: docker
# pipeline_stages:
# - docker: {}
# static_configs:
# - labels:
# job: docker
# path: /var/lib/docker/containers/*/*-json.log
cd ~
sudo docker-compose up -d --force-recreate
sudo docker ps
Go to Grafana
Add New Data Source… → Loki
http://loki:3100
Explore {job=varlogs} |= “Network”
scrape_configs:
- job_name: docker
pipeline_stages:
- docker: {}
static_configs:
- labels:
job: docker
path: /var/lib/docker/containers/*/*-json.log
Install a Docker Driver https://grafana.com/docs/loki/latest/clients/docker-driver/
docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions
sudo docker plugin ls
Check config: https://grafana.com/docs/loki/latest/clients/docker-driver/configuration/
{
"debug" : true,
"log-driver": "loki",
"log-opts": {
"loki-url": "http://localhost:3100/loki/api/v1/push",
"loki-batch-size": "400"
}
}
vim /etc/docker/daemon.json
sudo systemctl restart docker
Components of k8s Controller Manager
ETCD in k8s
Kubernetes Ingress Controller
Kubernetes Jobs & CronJobs
Kube-apiserver Description
Namespaces in Kubernetes
Pod commands, arguments, env variables
Kubernetes Pods Description
Kubernetes ReplicaSets & Deployments description
Kubernetes Services
Мастер нода представляет службы управления (Control Plane)
Ноды-работники размещают у себя контейнеры через Container Runtime Interface (CRI) с поддержкой containerd (через него Docker, с версии k8s 1.24+) и Rocket:
Kubeadm не устанавливает автоматически Kubelet-ы. Они всегда ставятся вручную на worker nodes.
Проверка и решение проблем с рабочими нодами. В отличие от утилит Docker, crictl понимает pods.
crictl images # список образов
circtl ps -a # список контейнеров
crictl exec -i -t 288023742....aaabb392849 ls # запуск команды в контейнере
crictl logs 288023742....aaabb392849 # посмотреть лог контейнера
crictl pods
Для написания YAML-файлов хорошо подходит редактор с плагином, понимающим k8s. Пример: VSCode + Red Hat YAML plugin
В свойствах плагина найти пункт Yaml: Schemas -> Edit in settings.json Добавить в конфиг:
{
"yaml.schemas": {
"kubernetes": "*.yaml"
},
"redhat.telemetry.enabled": true
}
Это позволит все файлы YAML редактировать с учётом полей, принятых для k8s.
Отслеживает состояние нод (через kube-apiserver):
Отслеживает множества replica sets, и что нужное число pods всегда доступны в каждом replica set.
# ETCD API v2.x
./etcdctl set key1 value1
./etcdctl get key1
./etcdctl --version # важно увидеть версию API (2), от этого команды зависят
# ETCD API v3.x
./etcdctl version # в API 3.x параметр version => команда, набирать без "--"
./etcdctl put key1 value1
./etcdctl get key1
./etcdctl get / --prefix -keys-only # вывести ключи в БД, без значений
export ETCDCTL_API=3 # поменять версию API на 3.х
Для аутентификации клиента ETCDCTL на ETCD API Server нужно указывать также сертификат:
--cacert /etc/kubernetes/pki/etcd/ca.crt
--cert /etc/kubernetes/pki/etcd/server.crt
--key /etc/kubernetes/pki/etcd/server.key
Отсюда полноценная команда-запрос клиента к ETCD будет выглядет вот так:
kubectl exec etcd-master -n kube-system -- sh -c "ETCDCTL_API=3 etcdctl get / --prefix --keys-only --limit=10 --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key"
Объект служит в роли прокси и балансировщика L7.
Создание минимального ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress-controller
spec:
replicas: 1
selector:
matchLabels:
name: nginx-ingress
template:
metadata:
labels:
name: nginx-ingress
spec:
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.21.0
args:
- /nginx-ingress-controller
- --configmap=$ (POD_NAMESPACE)/nginx_configuraruin
env: # nginx требует 2 переменные для конфигурации
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
Для конфигурации ingress на nginx также нужен ConfigMap. В него закладывается конфигурация nginx, которая в обычном варианте nginx как reverse-proxy вписывалась в config самого nginx:
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-configuration
Также необходимо создать Service, который публикует ingress вовне:
apiVersion: v1
kind: Service
metadata:
name: nginx-ingress
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
- port: 443
targetPort: 443
protocol: TCP
name: https
selector:
name: nginx-ingress
И нужен Service Account для аутентификации:
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-serviceaccounts
Набор правил ingress называются Ingress Resource.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-rules
spec:
backend:
serviceName: app-service
servicePort: 80
Определение нахождения правил ingress resource:
kubectl get ingress -A # найти Ingress Resource среди Namespaces
Редактирование правил ingress resource:
kubectl edit ingress <имя ingress resource> -n <namespace>
Настройка rewrite-target
нужна, чтобы правильно транслировать сетевой путь.
# Без rewrite-target:
http://<ingress-service>:<ingress-port>/watch --> http://<watch-service>:<port>/path
# С rewrite-target типа replace("/path","/"):
http://<ingress-service>:<ingress-port>/watch --> http://<watch-service>:<port>/
Для включения правил rewrite, нужно добавить в манифест annotations:
1. apiVersion: extensions/v1beta1
2. kind: Ingress
3. metadata:
4. name: test-ingress
5. namespace: critical-space
6. annotations: # применение rewrite правил
7. nginx.ingress.kubernetes.io/rewrite-target: /
8. spec:
9. rules:
10. - http:
11. paths:
12. - path: /pay
13. backend:
14. serviceName: pay-service
15. servicePort: 8282
Это объект для выполнения однократных служебных задач.
apiVersion: batch/v1
kind: Job
metadata:
name: my-job
spec:
completions: 3 # сколько Pod-ов запускать под задачу
parallelism: 3 # запускать Pod-ы не последовательно, а сразу пачками по 3
# если 1 из 3 Pod завершится с ошибкой, k8s будет 1 оставшийся перезапускать, # пока тот не закончит работу корректно
template:
spec:
containers:
- name: job-container
image: busybox
command: [ "/run/job" ]
restartPolicy: Never
Команды для работы с Jobs:
kubectl create -f <имя job.yaml>
kubectl get jobs
kubectl logs <имя Pod с Job> # вывод результата
kubectl delete job <имя Pod>
Объект для создания периодической задачи:
apiVersion: batch/v1
kind: CronJob
metadata:
name: my-cronjob
spec:
schedule: "*/1 * * * *" # работает как Cron в Linux, см ниже Cron Parameters
jobTemplate: # ниже описание spec обычного Job
spec:
completions: 3
parallelism: 3
template:
spec:
containers:
- name: job-container
image: busybox
command: [ "/run/job" ]
restartPolicy: Never
Cron parameters:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# │ │ │ │ │ 7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * <command to execute>
Работа kube-apiserver по созданию pod:
/etc/kubernetes/manifests/kube-apiserver.yaml
/etc/systemd/system/kube-apiserver.service
Use manifest to create:
apiVersion: v1
kind: Namespace
metadata:
name: dev
Commands:
kubectl create -f namespace-dev.yaml
kubectl create namespace dev
To place pods in selected namespace add it to their manifest:
apiVersion: v1
kind: Pod
metadata:
namespace: dev
name: myapp-pod
spec:
containers:
- name: nginx-container
image: nginx
Write the target namespace to get pods from:
kubectl get pods --namesapce=dev
Changing namespace in the current context of kubectl to dev:
kubectl config set-context $(kubectl config current-context) --namespace=dev
List pods in all namespaces:
kubectl get pods --all-namespaces
Команды и аргументы команд, которые срабатывают при запуске контейнера.
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
app: test_app
env: productinon
spec:
containers:
- name: nginx-container
image: nginx
command: [ "python3" ]
args: [ "app-test.py" ]
## Вариант 2
command:
- "python3"
- "app-test.py"
## Вариант 3
command: [ "python3", "app-test.py" ]
Заменять команды, аргументы, метки и т.д. нельзя. Однако, можно вызвать ошибку, потом пересоздать Pod на лету из сохранённого промежуточного файла:
$ kubectl edit pod nginx-container # отредактировал поле command
error: pods "nginx-container" is invalid
A copy of your changes has been stored to "/tmp/kubectl-edit-1395347318.yaml"
error: Edit cancelled, no valid changes were saved.
$ kubectl replace --force -f /tmp/kubectl-edit-1395347318.yaml
Переменные среды задаются как список, похожим образом с командами.
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
app: test_app
env: productinon
spec:
containers:
- name: nginx-container
image: nginx
env:
- name: APP_COLOR
value: green
Отдельный объект, который содержит переменные среды. Можно получить их список через kubectl get configmaps
apiVersion: v1
kind: ConfigMap
metadata:
name: mydb
data:
APP_COLOR: blue
APP_MODE: testdev
kubectl create configmap \
<имя конфига> --from-literal=<ключ>=<значение> \
--from-literal=APP_USER=testuser
kubectl create configmap \
<имя конфига> --from-file=<путь до файла>
# --from-file=app_config.properties
Ссылка производится по именам ConfigMap в виде списка:
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
app: test_app
env: productinon
spec:
containers:
- name: nginx-container
image: nginx
envFrom:
- configMapRef:
name: mydb
# Вариант взять только конкретную переменную:
env:
- name: APP_COLOR
valueFrom:
configMapKeyRef:
name: mydb
key: APP_COLOR
Секреты - это ConfigMap, значения которых кодируются по base64. Можно получить их список через:
kubectl get secrets # список секретов
kubectl describe secret <имя секрета> # не отображает значения
kubectl get secret <имя секрета> -o yaml # отображает значения в файле
apiVersion: v1
kind: Secret
metadata:
name: mydb
data:
APP_PWD: dmVyeXNlY3JldA== # base64 Encode
APP_TOKEN: dGVzdGRldg==
kubectl create secret generic \
<имя конфига> --from-literal=<ключ>=<значение> \
--from-literal=APP_USER=testuser
kubectl create secret generic \
<имя конфига> --from-file=<путь до файла>
# --from-file=app_config.properties
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
app: test_app
env: productinon
spec:
containers:
- name: nginx-container
image: nginx
envFrom:
- secretRef:
name: mydb
# Вариант взять только конкретное значение:
env:
- name: APP_COLOR
valueFrom:
secretKeyRef:
name: mydb
key: APP_PWD
# Вариант смонтировать как файлы (каждый пароль - отдельный файл)
volumes:
- name: app-secret-volume
secret:
secretName: app-secret
Специальные учётные записи для доступа к k8s. При создании вместе с ними создаётся объект secret.
kubectl create serviceaccount dashboard-sa
kubectl create token dashboard-sa # с k8s 1.24+ необходимо создать токен, у которого время жизни (по умолчанию) =1 час с момента создания
Readiness & Liveness Probes configuring and logging
Resource Requests & Limits
Pod and container security contexts
Node Selectors and Affinity
Pod taints and tolerations
Pod - наименьшая сущность в k8s. Обычно, pod = контейнер по принципу 1:1. Однако, можно несколько контейнеров разместить в 1 pod, при условии, что они функционально разные. Обычно это главный контейнер приложения и вспомогательные контейнеры, которые с ним связаны.
В обычном Docker, если развернуть множество копий “контейнер приложения” + “вспомогательный контейнер”, то нужно будет иметь карту взаимосвязей между ними всеми. Более того, в случае выхода из строя контейнера с приложением, нужно будет вручную удалять сопутствующий вспомогательный контейнер. От этого всего избавляют pod-ы, в рамках которых всё размещается, обеспечивается внутренняя связность, и далее k8s размножает готовые копии pod-ов в рамках кластера.
Pod-ы добавляют функционал к контейнерам:
Работа с pod-ами ведётся с помощью API или инструмента kubectl:
kubectl run nginx --image nginx # образ nginx будет скачан с DockerHub
kubectl get pods # список всех pod-ов и их статусов
kubectl get pods --selector app=App1 # отфильтровать вывод по заданному label
Создадим pod-definition.yml:
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
type: front-end
spec:
containers:
- name: nginx-container
image: nginx
Далее создаём pod командой:
kubectl create -f pod-definition.yml
kubectl get pods
Посмотреть доступные поля, подробную информацию о поле у pod:
kubectl explain pods --recursive
kubectl explain pod.spec.restartPolicy
Посмотреть конкретное поле у всех pod, например, образ image, из которого он сделан:
kubectl get pods -o jsonpath={.items[*].spec.containers[*].image}
Можно у работающего Pod получить спецификацию в YAML, из которой он сделан:
kubectl get pod <имя pod> -o yaml > pod-definition.yaml
kubectl delete pod <имя Pod> --now
Зайти внутрь Pod и выполнить команды:
kubectl exec -it <имя pod> -- /bin/sh
В конфигурацию pod можно добавить период обновления (например, 30 секунд) и установить “imagePullPolicy: “Always”. Удалить Pod с помощью kubectl delete pod pod_name
. Новый контейнер будет создан на последней версии образа, после чего старый контейнер будет удалён.
spec:
terminationGracePeriodSeconds: 30
containers:
- name: my_container
image: my_image:latest
imagePullPolicy: "Always"
Есть вариант “дёргать” за Deployment, вызывая тем самым обновление:
kubectl patch deployment <имя deployment> -p \
'{"spec":{"template":{"spec":{"terminationGracePeriodSeconds":31}}}}'
Если необходимо, чтобы Pod поработал и выключился, без перезапуска, то необходимо поменять его restartPolicy
, которая по умолчанию стоит в Always
- то есть перезапуск всегда по завершении работы.
spec:
containers:
- name: my_container
image: my_image:latest
restartPolicy: Never # ещё вариант OnFailure
В отличие от декларативных, такие команды позволяют быстро решить однократную задачу.
kubectl run nginx --image=nginx --dry-run=client -o yaml # --dry-run=client - не создаёт объект, сообщает о возможности его создания
kubectl run httpd --image=httpd:alpine --port=80 --expose=true # создать Pod из образа httpd:alpine и к нему сразу создать ClusterIP с публикацией порта
Несколько контейнеров в 1 POD делят один адрес IP (между собой они общаются через адаптер localhost), хранилище. Есть несколько типовых сценариев:
PODы стартуют атомарно - только после успешного старта всех контейнеров POD считается запущенным. Частичный запуск не допускается. POD целиком всеми контейнерами размещается на одной ноде worker.
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: nginx-container
image: nginx
ports:
- containerPort: 8080
- name: log-agent
image: log-agent
InitContainer - не живёт постоянно, а выполняется ДО загрузки остальных контейнеров в Pod, поэтому его инициализация - в отдельной секции:
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: nginx
ports:
- containerPort: 8080
initContainers:
- name: init-service
image: busybox
command: [ 'sh', '-c', 'git clone <some repo to be used by app>' ]
Если таких InitContainer несколько, они будут выполняться последовательно один за другим. Если любой InitContainer не сможет выполниться успешно, k8s будет перезапускать Pod, пока InitContainer успешно не завершится.
Если в кластере нет распределения, можно указать вручную параметр nodeName:
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
spec:
containers:
- name: nginx-container
image: nginx
nodeName: node01
Без указания этого параметра в отсутствии распределения Pod будет висеть как Pending. K8s не даст указать этот параметр на лету, после добавления Pod надо заменить kubectl replace --force -f nginx-pod.yaml
Определение, что ПО в контейнер действительно запустилось успешно и готово принимать данные пользователей, можно провести по-разному, добавив в манифест раздел spec -> containers
поле readinessProbe
.
Для проверки HTTP сервера:
readinessProbe:
httpGet:
path: /api/ready
port: 8080
initialDelaySeconds: 10 # предусматриваем 10 сек задержку на старте
periodSeconds: 5 # повторяем проверку спустя 5 секунд
failureThreshold: 8 # повторяем проверку 8 раз (по умолчанию 3)
Для проверки открытого порта (например, у СУБД):
readinessProbe:
tcpSocket:
port: 3306
Для проверки с помощью своей команды:
readinessProbe:
exec:
command:
- cat
- /app/is_ready
Периодическое определение, работает ли ПО в контейнере - для случаев, когда падение ПО не приводит к его вылету и закрытию контейнера.
Для проверки HTTP сервера:
livenessProbe:
httpGet:
path: /api/health_status
port: 8080
initialDelaySeconds: 10 # предусматриваем 10 сек задержку на старте
periodSeconds: 5 # повторяем проверку спустя 5 секунд
failureThreshold: 8 # повторяем проверку 8 раз (по умолчанию 3)
Для проверки открытого порта (например, у СУБД):
livenessProbe:
tcpSocket:
port: 3306
Для проверки с помощью своей команды:
livenessProbe:
exec:
command: [ "cat", "/app/is_working" ]
Для получения логов с Pod:
kubectl logs {-f} <pod name> <container name> # -f = tail
kubectl logs -f myapp logger # пример, в случае нескольких контейнеров в Pod выбран контейнер logger
Запрос контейнеров на гарантированные ресурсы.
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
name: myapp
spec:
containers:
- name: mywebapp
image: nginx
resources:
requests:
memory: "4Gi" # 4 гибибайта
cpu: 2 # минималка 0.1 CPU
Указание ограничений. При переходе лимита по CPU скорость для Pod замедляется (throttling). При переходе лимита по RAM происходит убийство Pod с ошибкой OOM Error (Out-Of-Memory).
apiVersion: v1
kind: Pod
metadata:
name: myapp
labels:
name: myapp
spec:
containers:
- name: mywebapp
image: nginx
resources:
requests:
memory: "2Gi"
cpu: 2
limits:
memory: "4Gi"
cpu: 2
В описании Pod можно указать ID пользователя, который запускает контейнеры, а также описать его возможности (capabilities).
Уровень Pod:
apiVersion: v1
kind: Pod
metadata:
name: web-pod
spec:
securityContext:
runAsUser: 1001
containers:
- name: ubuntu
image: ubuntu
Уровень контейнера:
apiVersion: v1
kind: Pod
metadata:
name: web-pod
spec:
containers:
- name: ubuntu
image: ubuntu
securityContext:
runAsUser: 1001
capabilities:
add: ["MAC_ADMIN"]
# возможности можно определить ТОЛЬКО на уровне контейнера
Добавить пометки к node можно командой:
kubectl label nodes <node-name> <label-key>=<label-value>
kubectl label nodes node-01 size=Large # пример
После этого в описании Pod можно указать Node Selector:
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: data-processor
image:: data-processor
nodeSelector:
size: Large
Node Selectors работают по принципу 1:1 совпадения метки Node и Pod. Для более сложных сценариев применяют Node Affinity.
Для создание свойств Node Affinity нужно поменять свойства в манифесте Pod:
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: data-processor
image:: data-processor
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: size
operator: In # условие, может быть наоборот NotIn, или Exists - есть ли вообще такой label, необязательно имеющий значение?
values:
- Large # условие действует на любое из значений списка
- Medium
В случае, если нужные labels отсутствуют на Nodes кластера, есть 2 типа поведения, которое задаётся свойством Pod:
requiredDuringSchedulingIgnoredDuringExecution
- если Nodes с нужными labels нет, вообще не размещать данный Pod на кластере;preferredDuringSchedulingIgnoredDuringExecution
- если Nodes с нужными labels нет, всё равно разместить данный Pod где-нибудь на кластере.Если Pod уже запущен на Node в момент, когда добавили label, то в версии 1.27 ничего не произойдёт в обоих случаях. В плане добавить третий тип поведения:
requiredDuringSchedulingRequiredDuringExecution
- если во время работы Pod произойдёт изменение affinity - удалить Pod с Node.Для распределения Pods по Nodes применяется сочетание покраски (taint) и восприимчивости (toleration) к ней.
Покраска Node говорит kube-scheduler, что есть 1 из 3 эффектов:
Покраска node:
kubectl taint nodes <имя node> key=value:effect
kubectl taint nodes node01 app=myapp:NoSchedule # пример
kubectl taint nodes node01 app=myapp:NoSchedule- # минус в конце снимает покрас
Поменять восприимчивость Pod к покраске:
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
spec:
containers:
- name: nginx-controller
image: nginx
tolerations:
- key: "app"
operator: "Equal"
value: "blue"
effect: "NoSchedule"
ReplicaSet следит за тем, чтобы количество Pod всегда было заданным числом штук (параметр replicas) - не больше и не меньше. ReplicaSet Controller является более современным аналогом ReplicationController:
Отличие ReplicaSet в том, что в нём обязательным параметром есть selectors, который отслеживает контейнеры, в том числе созданные ДО создания самого ReplicaSet. Отслеживание можно производить по их меткам labels. Для совпадающих меток работает алгоритм приведения к нужному количеству.
Создание ReplicaSet:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myreplicaset
labels:
name: myapp
spec:
selector:
matchLabels:
env: production ### метка должна совпадать
replicas: 3
template:
metadata: ### шаблон контейнера берётся из описания Pod
name: myapp
labels:
env: production ### метка должна совпадать
spec:
containers:
- name: nginx-container
image: nginx
Шаблон Pod всегда должен быть описан в части template, чтобы ReplicaSet знал, какие Pod создавать.
kubectl create -f <имя файла с описанием replicaset>
kubectl get rs # вывести все ReplicaSet в кластере
kubectl describe rs <имя replicaset> # подробности о ReplicaSet
kubectl delete rs <имя replicaset> # удаляет все Pods и сам ReplicaSet
kubectl edit rs <имя replicaset> # отредактировать описание Replicaset
kubectl scale rs <имя replicaset> --replicas=<новое количество копий>
kubectl replace -f <имя файла с описанием replicaset> # заменить ReplicaSet
Deployment - это надстройка над ReplicaSet, добавляет логику обновления Pod с одной версии на другую. При обновлении Deployment имеет 2 стратегии:
kubectl apply -f <имя Deployment> # запустить процесс rollout после внесения изменений в манифест
kubectl rollout status <имя Deployment> # узнать о статусе выкатывания
kubectl rollout history <имя Deployment> # узнать о всех ревизиях и причинах их перехода
kubectl rollout history <deployment> --revision=1 # узнать статус конкретной версии
kubectl rollout undo <имя Deployment> # откатить назад обновление Deployment
kubectl rollout undo <имя Deployment> --to-revision=1 # откатить до версии 1
apiVersion: apps/v1
kind: Deployment
metadata:
name: mydeployment
labels:
name: myapp
spec:
selector:
matchLabels:
env: production ### метка должна совпадать
replicas: 3
strategy:
type: RollingUpdate ### стратегия замены Pods
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata: ### шаблон контейнера берётся из описания Pod
name: myapp
labels:
env: production ### метка должна совпадать
spec:
containers:
- name: nginx-container
image: nginx
kubectl create -f <имя файла с описанием Deployment>
kubectl get deploy # вывести все Deployment в кластере
kubectl describe deploy <имя Deployment> # подробности о Deployment
kubectl delete rs <имя Deployment> # удаляет все Pods и сам Deployment
kubectl edit rs <имя Deployment> # отредактировать описание Deployment и произвести его обновление
kubectl edit rs <имя Deployment> --record # отредактировать описание, вызвав обновление и записать команду как причину обновления в список ревизий
kubectl set image deploy <имя Deployment> nginx=nginx:1.18 # пример обновления без редактирования YAML
В отличие от декларативных, такие команды позволяют быстро решить однократную задачу.
kubectl create deploy nginx --image=nginx --replicas=4
kubectl scale deploy nginx --replicas=4
kubectl create deployment nginx --image=nginx --dry-run=client -o yaml > nginx-deployment.yaml # вывести манифест YAML для последующего редактирования
Service - логический объект в k8s, который позволяет внешним клиентам подключаться к сетевым портам на контейнерах внутри кластеров Pod.
Service делятся на 3 вида:
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30004 # порты ограничены диапазоном 30000-32767
selector:
app: myapp
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
selector:
app: myapp
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
type: LoadBalancer # only works with a supported platform
ports:
- port: 80
targetPort: 80
selector:
app: myapp
kubectl create -f <имя файла с описанием Service>
kubectl get svc # вывести все Service в кластере
kubectl describe svc <имя service> # подробности о service
kubectl delete svc <имя service> # удаляет все объект service
kubectl edit svc <имя service> # отредактировать описание service и произвести его обновление
В отличие от декларативных, такие команды позволяют быстро решить однократную задачу.
kubectl create service clusterip redis --tcp=6379:6379 --dry-run=client -o yaml # нельзя подавать selectors в команду, следует вывести YAML и отредактировать, что небыстро
kubectl expose pod redis --port=6379 --name=redis-service --dry-run=client -o yaml # такой вариант CLusterIP использует labels самого pod как selectors, что намного эффективнее
kubectl expose pod nginx --port=80 --name=nginx-service --type=NodePort --dry-run=client -o yaml # такой вариант NodePort использует labels самого pod как selectors