Translate

вівторок, 8 березня 2022 р.

Kubernetes. Part III: Objects And How To Use Them

Отже у нас вже є готовий налаштований кластер і, озброївшись необхідним теоретичним мінімумом, ми можемо переходити до подальшого вивчення Kubernetes. Цього разу ми поговоримо детальніше про основні примітиви (objects) Kubernetes, як вони взаємодіють між собою та як реалізовані.

Забігаючи наперед, основні команд для отримання даних про роботу об'єктів є наступні:
  • kubectl get object_type - перерахунок всіх об'єктів даного типу, котрі знаходяться в межах одного неймспейсу
  • kubectl describe object_type - детальна інформація про всі об'єкт даного типу, що також знаходяться в одному неймспейсі
Всі основні об'єкти перераховані нижче. Якщо замість object_type вказати all, то буде виведено все, що знаходиться в неймспейсі.

ПОД (POD). Створимо под, що буде складатись з одного docker-контейнера і буде доступний на порту 9876. Для цього використаємо образ з dockerhub mhausenblas/simpleservice:0.5.0:

$ kubectl run first-pod --image=mhausenblas/simpleservice:0.5.0 --port=9876

deployment "first-pod" created


Перевіримо чи новий под працює:

$ kubectl get pods

NAME                         READY     STATUS    RESTARTS   AGE
first-pod-2257828502-l6z96   1/1       Running   0          23s

$ kubectl describe pod first-pod-2257828502-l6z96 | grep IP:

IP:   10.34.0.25

Отже, в межах кластеру под працює за адресою 10.34.0.25. Із будь-якого вузла кластеру пересвідчимось в цьому, зробивши запит до додатку:

[cluster]$ curl 10.34.0.25:9876/info
{"host": "10.34.0.25:9876", "version": "0.5.0", "from": "10.32.0.1"}

Звісно зручніше створювати поди використовуючи yaml-опис ресурсу:

$ vim second-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: second-pod
spec:
  containers:
  - name: sise
    image: mhausenblas/simpleservice:0.5.0
    ports:
    - containerPort: 9876
    resources:
      limits:
        memory: "64Mi"
        cpu: "500m"
  - name: shell
    image: centos:7
    command:
      - "bin/bash"
      - "-c"
      - "sleep 10000"

$ kubectl apply -f second-pod.yaml

pod "second-pod" created

$ kubectl get pods

NAME              READY     STATUS    RESTARTS   AGE
second-pod        2/2       Running   0          16s

Як видно з опису, под складається з 2 контейнерів: із іменем sise та shell. Перший контейнер також має встановлені ліміти використання пам’яті та центрального процесору (64MB RAM та 0.5 CPUs). Знаючи ім’я поду та контейнера, можна отримати термінальний доступ до нього:

$ kubectl exec second-pod -c sise -i -t -- bash

[root@second-pod /]# curl localhost:9876/info
{"host": "localhost:9876", "version": "0.5.0", "from": "127.0.0.1"}


Синтаксис майже аналогічний тому, що використовується для доступу до контейнерів Docker.

Більше сервісних даних можна отримати за допомогою наступної команди:

$ kubectl describe pod second-pod

Name:   second-pod
Namespace:  default
Node:   k8s-s3/192.168.60.113
Start Time: Sun, 01 Oct 2017 17:25:08 -0400
Labels:   <none>
Annotations:  ...
Status:   Running
IP:   10.34.0.38
Containers:
  sise:
    Container ID: ...
    Image:    mhausenblas/simpleservice:0.5.0
    Image ID: ...
    Port:   9876/TCP
    State:    Running
      Started:    Sun, 01 Oct 2017 17:25:08 -0400
    Ready:    True
    Restart Count:  0
    Limits:
      cpu:  500m
      memory: 64Mi
    Requests:
      cpu:    500m
      memory:   64Mi
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-k016d (ro)
  shell:
    Container ID: ...
    Image:    centos:7
    Image ID: ...
    Port:   <none>
    Command:
      bin/bash
      -c
      sleep 10000
    State:    Running
      Started:    Sun, 01 Oct 2017 17:25:08 -0400
    Ready:    True
    Restart Count:  0
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-k016d (ro)
Conditions:
  Type    Status
  Initialized   True
  Ready   True
  PodScheduled  True
Volumes:
  default-token-k016d:
    Type: Secret (a volume populated by a Secret)
    SecretName: default-token-k016d
    Optional: false
QoS Class:  Burstable
Node-Selectors: <none>
Tolerations:  node.alpha.kubernetes.io/notReady:NoExecute for 300s
    node.alpha.kubernetes.io/unreachable:NoExecute for 300s
Events:
  FirstSeen LastSeen  Count From      SubObjectPath   Type    Reason      Message
  --------- --------  ----- ----      -------------   --------  ------      -------
...
  4m    4m    1 kubelet, k8s-s3   spec.containers{shell}  Normal    Started     Started container

Є можливість вручну вказувати вузол, на котрому необхідно запустити под. Це робиться наступним чином:

$ kubectl label nodes k8s-s1 shouldrun=here

node "k8s-s1" labeled

Ми призначили мітку shouldrun=here вузлу k8s-s1. Після чого опишемо і запустимо под, котрий буде запущено цьому вузлі:

$ vim specific-pod-node.yaml

apiVersion: v1
kind: Pod
metadata:
  name: specific-pod-node
spec:
  containers:
  - name: sise
    image: mhausenblas/simpleservice:0.5.0
    ports:
    - containerPort: 9876
  nodeSelector:
    shouldrun: here

$ kubectl apply -f specific-pod-node.yaml

$ kubectl get pods --output=wide

NAME                    READY     STATUS    RESTARTS   AGE       IP           NODE
specific-pod-node       1/1       Running   0          1m        10.40.0.34   k8s-s1


Для контейнерів в поді є механізм перевірки роботи додатків (health checks). Probes, як їх називають в Kubernetes, виконуються процесом kubelet для того, щоб визначити чи потрібно його перестворити (livenessProbe) у разі проблем з роботою; чи ініціюються сервісом (об'єкт Kubernetes), щоб зрозуміти чи має под отримувати трафік (readinessProbe) і т.п. Піднімемо наступний под для демонстрації підключення хелсчеків:

$ vim pod-healthcheck1.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-healthcheck1
spec:
  containers:
  - name: sise
    image: mhausenblas/simpleservice:0.5.0
    ports:
    - containerPort: 9876
    livenessProbe:
      initialDelaySeconds: 2
      periodSeconds: 5
      httpGet:
        path: /health
        port: 9876

$ kubectl apply -f pod-healthcheck1.yaml

$ kubectl get pods

NAME               READY     STATUS    RESTARTS   AGE
pod-healthcheck1   1/1       Running   0          7s


Тут потрібно звернути увагу на секцію livenessProbe, де вказано, що через 2 секунди після старту контейнера sise, кожні 5 секунд на порт 9876 буде надсилатись GET-запит по шляху /health. У разі повернення не 200 коду контейнер буде перестворено автоматично. Звісно, що для роботи цього механізму додаток має бути написаний відповідним чином, тобто на запит на 9876 порт та вищезгаданий шлях програма має віддавати коректний статус код.

Перевірка readinessProbe оголошується майже аналогічно:

$ vim pod-healthcheck2.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-healthcheck2
spec:
  containers:
  - name: sise
    image: mhausenblas/simpleservice:0.5.0
    ports:
    - containerPort: 9876
    readinessProbe:
      initialDelaySeconds: 10
      httpGet:
        path: /health
        port: 9876

$ kubectl apply -f pod-healthcheck2.yaml

У цьому ж разі под pod-healthcheck2 буде ввімкнений в сервіс (тобто на нього буде спрямовано трафік) лише у разі повернення коду 200 від додатку. Запуск першої перевірки відбудеться через 10 секунд після старту поду (на випадок якщо необхідно завантажити якісь дані з 3-го сервісу чи щось на зразок цього). Більше про хелсчеки можна почитати за посиланням https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/.

Для того, щоб відправити довільну змінну середовища, до опису поду має бути додана секція env:

$ vim pod-env.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-env
spec:
  containers:
  - name: sise
    image: mhausenblas/simpleservice:0.5.0
    ports:
    - containerPort: 9876
    env:
    - name: SIMPLE_SERVICE_VERSION
      value: "1.0"

$ kubectl apply -f pod-env.yaml

$ kubectl exec pod-env -- printenv

PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=pod-env
SIMPLE_SERVICE_VERSION=1.0
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
LANG=C.UTF-8
GPG_KEY=C01E1CAD5EA2C4F0B8E3571504C367C218ADD4FF
PYTHON_VERSION=2.7.13
PYTHON_PIP_VERSION=9.0.1
REFRESHED_AT=2017-04-24T13:50
HOME=/root


Проте виливати поди напряму не найкраща ідея, адже в такому разі не буде доступне їх перестворення у разі падіння, масштабування, зручний деплой/ролбек коду, зміни їх параметрів і т.п. Тому варто створювати поди в контексті deployment примітиву.


МІТКА ТА СЕЛЕКТОР (LEBEL, SELECTOR). Мітки до об’єктів Kubernetes можуть призначатись виходячи з дуже різноманітної логіки. Наприклад, мітка може вказувати на те, хто є відповідальним за роботу поду чи до якого середовища він відноситься (dev, prod). Окрім того, виходячи з назви мітки, до об’єктів можуть бути прив’язані інші об’єкти за допомогою опції selector. Наприклад, до подів з міткою app: sise може бути прив’язаний сервіс з таким же селектором.

Для прикладу створимо под з міткою env=development:

$ vim pod-label.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-label
  labels:
    env: development
spec:
  containers:
  - name: sise
    image: mhausenblas/simpleservice:0.5.0
    ports:
    - containerPort: 9876

$ kubectl apply -f pod-label.yaml


Перевіримо чи була встановлена мітка:

$ kubectl get pods --show-labels

NAME               READY     STATUS    RESTARTS   AGE       LABELS
pod-label          1/1       Running   0          32s       env=development


Мітки можуть бути додані до вже створених подів:

$ kubectl label pods pod-label owner=ipeacocks

pod "pod-label" labeled

$ kubectl get pods --show-labels

NAME        READY     STATUS    RESTARTS   AGE       LABELS 
pod-label   1/1       Running   0          6m        env=development,owner=ipeacocks

Виходячи з доступних міток можна робити вибірки:

kubectl get pods --selector owner=ipeacocks

NAME        READY     STATUS    RESTARTS   AGE
pod-label   1/1       Running   0          7m

Ці вибірки в тому числі можуть бути комплексні. На зразок наступної операції OR:

$ kubectl get pods -l 'env in (production, development)'


-l - скорочений варіант опції --selector.


НАБІР/МНОЖИНА РЕПЛІК (REPICA SETS). Рівень абстракції над подами, котрий можна створити наступним чином:

$ vim replicaset-x.yaml

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: replicaset-x
spec:
  replicas: 3
  selector:
    matchLabels:
      app: replicaset-x
  template:
    metadata:
      name: replicaset-x
      labels:
        app: replicaset-x
    spec:
      containers:
      - name: sise
        image: mhausenblas/simpleservice:0.5.0
        ports:
        - containerPort: 9876


$ kubectl apply -f replicaset-x.yaml


Отже, було створено набір реплік replicaset-x, котрий має складатись із 3 однакових подів зі встановленою міткою app: replicaset-x, на що вказує опція matchLabels. Перевіримо це:

$ kubectl get rs

NAME           DESIRED   CURRENT   READY     AGE
replicaset-x   3         3         3         37s

$ kubectl get pods --show-labels

NAME                 READY     STATUS    RESTARTS   AGE       LABELS
replicaset-x-39t0n   1/1       Running   0          2m        app=replicaset-x
replicaset-x-dm96t   1/1       Running   0          2m        app=replicaset-x
replicaset-x-mqdll   1/1       Running   0          2m        app=replicaset-x


Звісно, що можна як збільшувати кількість подів у репліках, так і зменшувати:

$ kubectl scale --replicas=1 rs replicaset-x

replicaset "replicaset-x" scaled

$ kubectl get pods -l app=replicaset-x

NAME                 READY     STATUS        RESTARTS   AGE
replicaset-x-39t0n   1/1       Running       0          4m
replicaset-x-dm96t   1/1       Terminating   0          4m
replicaset-x-mqdll   1/1       Terminating   0          4m


Знову ж виливати поди напряму через ReplicaSet не рекомендується. ReplicaSet вже входить в контекст об’єкту Deployment, який наглядає за історією подів (rolling-update команда) та кількістю їх інстансів.


ДЕПЛОЙМЕНТ (DEPLOYMENT). Створюється наступним чином:

$ vim deployment-09.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-09
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sise
  template:
    metadata:
      labels:
        app: sise
    spec:
      containers:
      - name: sise
        image: mhausenblas/simpleservice:0.5.0
        ports:
        - containerPort: 9876
        env:
        - name: SIMPLE_SERVICE_VERSION
          value: "0.9"

$ kubectl apply -f deployment-09.yaml
deployment "deployment-09" created


Цей деплоймент одразу призведе до виливки реплікасету та подів в ньому:

$ kubectl get deploy

NAME            DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment-09   2         2         2            2           17s

$ kubectl get rs

NAME                       DESIRED   CURRENT   READY     AGE
deployment-09-4084969740   2         2         2         30s

$ kubectl get pods

NAME                             READY     STATUS    RESTARTS   AGE
deployment-09-4084969740-g4756   1/1       Running   0          34s
deployment-09-4084969740-gw4df   1/1       Running   0          34s


Через змінну середовища (env) було передано версію сервісу, що працює в контейнері поду. Перевіримо чи вищезгаданий сервіс справді повертає це значення:

$ kubectl describe pod deployment-09-4084969740-g4756

Name:   deployment-09-4084969740-g4756
Namespace:  default
Node:   k8s-s2/192.168.60.112
Start Time: Sun, 01 Oct 2017 19:42:59 -0400
Labels:   app=sise
    pod-template-hash=4084969740
...
Status:   Running
IP:   10.38.0.35
Created By: ReplicaSet/deployment-09-4084969740
Controlled By:  ReplicaSet/deployment-09-4084969740
Containers:
  sise:
...
    Environment:
      SIMPLE_SERVICE_VERSION: 0.9
...

[cluster] $ curl 10.38.0.35:9876/info
{"host": "10.38.0.35:9876", "version": "0.9", "from": "10.32.0.1"}

Відредагуємо змінну SIMPLE_SERVICE_VERSION і встановимо її в значення 1.0:

$ vim deployment-09.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-09
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sise
  template:
    metadata:
      labels:
        app: sise
    spec:
      containers:
      - name: sise
        image: mhausenblas/simpleservice:0.5.0
        ports:
        - containerPort: 9876
        env:
        - name: SIMPLE_SERVICE_VERSION
          value: "1.0"

Після чого застосуємо зміни, на попередній деплоймент:

$ kubectl apply -f deployment-09.yaml

ReplicaSet-и та прості поди не дозволено оновлювати після змін параметрів. Єдиний варіант - це перезалити їх заново.

Окрім оновлення yaml-у виливки можна було напряму відредагувати деплоймент за допомогою наступної команди:

$ kubectl edit deployment deployment-09

У процесі оновлення буде помітно як створюються нові поди та реплікасет:

$ kubectl get pods

NAME                             READY     STATUS        RESTARTS   AGE
deployment-09-2996259344-m887q   1/1       Running       0          24s
deployment-09-2996259344-qwd0f   1/1       Running       0          24s
deployment-09-4084969740-g4756   1/1       Terminating   0          3h
deployment-09-4084969740-gw4df   1/1       Terminating   0          3h

$ kubectl get rs

NAME                     DESIRED   CURRENT   READY     AGE
sise-deploy-2958877261   2         2         2         28s
sise-deploy-3513442901   0         0         0         3h


Попередні поди будуть зупинені, а нові будуть запущені з новою змінною середовища SIMPLE_SERVICE_VERSION.

Впродовж виливки нового деплойменту за прогресом цього процесу можна спостерігати наступною командою:

$ kubectl rollout status deployment deployment-09

Waiting for rollout to finish: 1 out of 2 new replicas have been updated...
deployment "deployment-09" successfully rolled out


Як і в попередньому випадку, за допомогою 'kubectl describe pod' дізнаємось про нову адресу поду, щоб пересвідчитись на практиці, що зміни в додатку відбулись:

$ kubectl describe pod deployment-09-2996259344-m887q | grep IP:

IP:   10.34.0.38

[cluster] $ curl 10.34.0.38:9876/info
{"host": "10.34.0.38:9876", "version": "1.0", "from": "10.32.0.1"}


Kubernetes веде історію всіх оновлень деплойментів:

kubectl rollout history deployment deployment-09

deployments "deployment-09"
REVISION  CHANGE-CAUSE
1   <none>
2   <none>


У разі проблем з виливкою змін Kubernetes самостійно поверне останню стабільну версію, але ж і ніхто не забороняє зробити це самостійно:

$ kubectl rollout undo deployment deployment-09 --to-revision=1

deployment "deployment-09" rolled back

$ kubectl rollout history deployment deployment-09

deployments "deployment-09"
REVISION        CHANGE-CAUSE
2               <none>
3               <none>

$ kubectl get pods

NAME                           READY     STATUS    RESTARTS   AGE
deployment-09-3513442901-ng8fz   1/1       Running   0          1m
deployment-09-3513442901-s8q4s   1/1       Running   0          1m


Як і реплікасети, деплойменти можуть легко масштабуватись горизонтально:

$ kubectl scale --replicas=3 deployment deployment-09

deployment "deployment-09" scaled

Для того щоб видалити всі поди та набір реплік у цьому разі варто лише видалити deployment:

$ kubectl delete deployment deployment-09

deployment "deployment-09" deleted


СЕРВІС (SERVICE). Сервіс прив'язується до вже працюючих подів із відповідними мітками. Справа в тому, що кожна наступна виливка тих же подів зазвичай призводить до того, що їхні адреси в оверлейній мережі змінюються. Сервіс же слідкує за цим і завдяки процесу kube-proxy динамічно перебудовує iptables-правила доступу до кінцевих подів.

Розглянемо на прикладі як це відбувається. Створимо деплоймент із іменем deployment-for-service:

$ vim deployment-for-service.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-for-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sise
  template:
    metadata:
      labels:
        app: sise
    spec:
      containers:
      - name: sise
        image: mhausenblas/simpleservice:0.5.0
        ports:
        - containerPort: 9876

$ kubectl apply -f deployment-for-service.yaml

Та сервіс із іменем service-for-pods:

$ vim service-for-pods.yaml

apiVersion: v1
kind: Service
metadata:
  name: service-for-pods
spec:
  selector:
    app: sise
  ports:
    - port: 80
      targetPort: 9876

$ kubectl apply -f service-for-pods.yaml
service "service-for-pods" created


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

$ kubectl get pods -l app=sise

NAME                                      READY     STATUS    RESTARTS   AGE
deployment-for-service-3136624256-q49fv   1/1       Running   0          15m

$ kubectl describe pod deployment-for-service-3136624256-q49fv

Name:           deployment-for-service-3136624256-q49fv
Namespace:      default
Node:           k8s-s3/192.168.60.113
Start Time:     Mon, 02 Oct 2017 07:30:50 +0300
Labels:         app=sise
                pod-template-hash=3136624256
...
Status:         Running
IP:             10.34.0.25
Created By:     ReplicaSet/deployment-for-service-3136624256
Controlled By:  ReplicaSet/deployment-for-service-3136624256
Containers:
...

З виводу останньої команди бачимо пряму адресу контейнера в оверлейній мережі 10.34.0.25. Перевіримо чи працює сервіс в поді:

[cluster] $ curl 10.34.0.25:9876/info
{"host": "10.34.0.25:9876", "version": "0.5.0", "from": "10.32.0.1"}


Ця адреса буде актуальною лише в межах життєвого циклу поду, котрий може обірватись його перестворенням через деплой нової версії коду, можливі помилки роботи додатку і т.п. Перевіримо адресу щойно створеного сервісу simpleservice:

$ kubectl get svc service-for-pods

NAME               CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service-for-pods   10.103.222.198   <none>        80/TCP    23m

$ kubectl describe svc service-for-pods

Name:     service-for-pods
Namespace:    default
Labels:     <none>
...
Selector:   app=sise
Type:     ClusterIP
IP:     10.103.222.198
Port:     <unset> 80/TCP
Endpoints:    10.34.0.25:9876
Session Affinity: None
Events:     <none>


Отже наразі поди, що мають мітку app=sise будуть доступні за адресою 10.103.222.198:

[cluster] $ curl 10.103.222.198:80/info
{"host": "10.103.222.198", "version": "0.5.0", "from": "10.32.0.1"}


Адреса 10.103.222.198 віртуальна, тобто не прив'язана до жодного інтерфейсу. Вона маршрутизується до адрес подів завдяки правилам iptables, котрі створює kube-proxy, і надалі слідкує за ними. Ці правила зручно переглядати завдяки коментарям, в котрих міститься ім'я сервісу, що призвів до їх створення:

[cluster] $ sudo iptables-save | grep service-for-pods
-A KUBE-SEP-XT3CJYIQSQHZ6QSZ -s 10.34.0.25/32 -m comment --comment "default/service-for-pods:" -j KUBE-MARK-MASQ
-A KUBE-SEP-XT3CJYIQSQHZ6QSZ -p tcp -m comment --comment "default/service-for-pods:" -m tcp -j DNAT --to-destination 10.34.0.25:9876
-A KUBE-SERVICES -d 10.103.222.198/32 -p tcp -m comment --comment "default/service-for-pods: cluster IP" -m tcp --dport 80 -j KUBE-SVC-2LEWRE2R3GXDG7LY
-A KUBE-SVC-2LEWRE2R3GXDG7LY -m comment --comment "default/service-for-pods:" -j KUBE-SEP-XT3CJYIQSQHZ6QSZ


Тож, виходячи з цих правил, TCP трафік до адреси 10.103.222.198:80 буде перенаправлятись до 10.34.0.25:9876, що і є нашим подом.

Масштабуємо попередній деплоймент до двох одиниць:

$ kubectl scale --replicas=2 deployment deployment-for-service

deployment "deployment-for-service" scaled

$ kubectl get pods -l app=sise -o wide

NAME                                      READY     STATUS    RESTARTS   AGE       IP           NODE
deployment-for-service-3136624256-q49fv   1/1       Running   0          1h        10.34.0.25   k8s-s3
deployment-for-service-3136624256-tt4sh   1/1       Running   0          9m        10.38.0.35   k8s-s2


Після чого знову перевіримо правила iptables:

[cluster] $ sudo iptables-save | grep service-for-pods
-A KUBE-SEP-XT3CJYIQSQHZ6QSZ -s 10.34.0.25/32 -m comment --comment "default/service-for-pods:" -j KUBE-MARK-MASQ
-A KUBE-SEP-XT3CJYIQSQHZ6QSZ -p tcp -m comment --comment "default/service-for-pods:" -m tcp -j DNAT --to-destination 10.34.0.25:9876
-A KUBE-SEP-Z7AWXXEVLG2GJLHC -s 10.38.0.35/32 -m comment --comment "default/service-for-pods:" -j KUBE-MARK-MASQ
-A KUBE-SEP-Z7AWXXEVLG2GJLHC -p tcp -m comment --comment "default/service-for-pods:" -m tcp -j DNAT --to-destination 10.38.0.35:9876
-A KUBE-SERVICES -d 10.103.222.198/32 -p tcp -m comment --comment "default/service-for-pods: cluster IP" -m tcp --dport 80 -j KUBE-SVC-2LEWRE2R3GXDG7LY
-A KUBE-SVC-2LEWRE2R3GXDG7LY -m comment --comment "default/service-for-pods:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-XT3CJYIQSQHZ6QSZ
-A KUBE-SVC-2LEWRE2R3GXDG7LY -m comment --comment "default/service-for-pods:" -j KUBE-SEP-Z7AWXXEVLG2GJLHC


З виводу видно нове правило, окрім ще одного доданого поду з адресою 10.38.0.35:

-A KUBE-SVC-EZC6WLOVQADP4IAW -m comment --comment "default/simpleservice:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-4SQFZS32ZVMTQEZV


Це правило вказує, що трафік буде розподілятись між двома подами порівну (--probability 0.50000000000), що стало можливим завдяки модулю iptables statistics. Звісно це не Round-robin в класичному розумінні. Почитати про це більше можна тут і тут.

Але це ще не все. Завдяки додатку kube-dns, що представлений у вигляді групи подів в системному неймспейсі, сервіси можуть взаємодіяти між собою по автоматично згенерованим доменним іменам. Продемонструю це на наступному прикладі. Створимо сервіс service-disc та деплоймент deploy-disc:

$ vim deploy-disc.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-disc
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sise
  template:
    metadata:
      labels:
        app: sise
    spec:
      containers:
      - name: sise
        image: mhausenblas/simpleservice:0.5.0
        ports:
        - containerPort: 9876

$ vim service-disc.yaml

apiVersion: v1
kind: Service
metadata:
  name: service-disc
spec:
  ports:
    - port: 80
      targetPort: 9876
  selector:
    app: sise

$ kubectl apply -f deploy-disc.yaml

$ kubectl apply -f service-disc.yaml

Припустимо тепер, що ми хочемо підключитись до сервісу service-disc з іншого поду кластера. Для того щоб продемонструвати це, створимо под в цьому ж неймспейсі (по-замовчуванню це default, адже ми його не вказуємо під час виконання команд):

$ vim jumpod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: jumpod
spec:
  containers:
  - name: shell
    image: centos:7
    command:
      - "bin/bash"
      - "-c"
      - "sleep 10000"

$ kubectl apply -f jumpod.yaml


Завдяки вищезгаданому плагіну сервіс service-disc буде доступним по FQDN service-disc.default.svc.cluster.local (або по трохи коротшому service-disc.default) для всіх подів кластеру і по короткому імені service-disc в межах одного неймспейсу:

$ kubectl exec jumpod -c shell -i -t -- ping service-disc.default.svc.cluster.local

PING service-disc.default.svc.cluster.local (10.110.53.67) 56(84) bytes of data.
...

[cluster] $ kubectl exec jumpod -c shell -i -t -- curl http://service-disc/info
{"host": "service-disc", "version": "0.5.0", "from": "10.38.0.35"}


Де 10.38.0.35 - адреса внутрішньої оверлейної мережі кластера Kubernetes. Отже, якщо розібрати ім'я домену service-disc.default.svc.cluster.local на складові, то service-disc - це ім'я сервісу, а default - неймспейс, в якому цей сервіс створено.


ТОМ (VOLUME). Це директорія, що доступна для всіх контейнерів одного поду. По-замовчуванню кожен контейнер володіє окремим дисковим простором, структура якого оголошується в docker-образі, котрий у разі передеплою чи падіння буде затертий і створений заново. Volume же дозволяє зберегти увесь зміст томів навіть після перестворення контейнерів у поді у разі падінь, але лише до моменту поки под не буде видалений власноруч. Типів томів існує велика множина, тому я продемонструю роботу лише одного типу - emptyDir. Для цього створимо под pod-volume із двох контейнерів:

$ vim pod-volume.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-volume
spec:
  containers:
  - name: c1
    image: centos:7
    command:
      - "bin/bash"
      - "-c"
      - "sleep 10000"
    volumeMounts:
      - name: xchange
        mountPath: "/tmp/xchange"
  - name: c2
    image: centos:7
    command:
      - "bin/bash"
      - "-c"
      - "sleep 10000"
    volumeMounts:
      - name: xchange
        mountPath: "/tmp/data"
  volumes:
  - name: xchange
    emptyDir: {}

$ kubectl apply -f pod-volume.yaml

$ kubectl describe pod pod-volume

Name:                   pod-volume
Namespace:              default
...
Volumes:
  xchange:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:

У кожному із контейнерів c1 та с2 буде примонтовано один диск по шляху /tmp/xchange та /tmp/data відповідно. Щоб перевірити, що це насправді так, зайдемо в перший контейнер, перевіримо чи примонтований диск та створимо текстовий файл на ньому:

$ kubectl exec pod-volume -c c1 -i -t -- bash

[root@pod-volume /]# mount | grep xchange
/dev/sda1 on /tmp/xchange type ext4 (rw,relatime,errors=remount-ro,data=ordered)
[root@pod-volume /]# echo 'some data' > /tmp/xchange/data

Із другого спробуємо переглядати зміст цього ж файлу:

$ kubectl exec pod-volume -c c2 -i -t -- bash

[root@pod-volume /]# mount | grep /tmp/data
/dev/sda1 on /tmp/data type ext4 (rw,relatime,errors=remount-ro,data=ordered)
[root@pod-volume /]# cat /tmp/data/data
some data


Як бачимо, практика відповідає теорії.


ПРОСТІР ІМЕН (NAMESPACE). Створюється так:

$ vim ns-test.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: test

$ kubectl apply -f ns-test.yaml

$ kubectl get ns

NAME          STATUS    AGE
default       Active    71d
ingress       Active    30d
kube-public   Active    71d
kube-system   Active    71d
test          Active    8s

$ kubectl describe ns default

Name:   default
Labels:   <none>
Annotations:  <none>
Status:   Active

No resource quota.
No resource limits.

Звісно нові об'єкти Kubernetes можна додавати до вже створеного неймспейсу за допомогою ключа --namespace:

$ vim podintest.yaml

apiVersion: v1
kind: Pod
metadata:
  name: podintest
spec:
  containers:
  - name: sise
    image: mhausenblas/simpleservice:0.5.0
    ports:
    - containerPort: 9876

$ kubectl apply --namespace=test -f podintest.yaml


Разом з тим бажаний неймспейс для об'єкту можна вказувати в тілі його yaml-опису:

$ vim podintest.yaml

apiVersion: v1
kind: Pod
metadata:
  name: podintest
  namespace: test


Щоб переглянути, наприклад, поди одного неймспейсу необхідно знову ж використати ключ --namespace:

$ kubectl get pods --namespace=test

NAME        READY     STATUS    RESTARTS   AGE
podintest   1/1       Running   0          16s


У цій статті ми користуємось неймспейсом default, тому ми, в основному, не вказуємо його імені. Видалення неймспейсу потягне за собою видалення всіх об'єктів, що працюють в його межах:

$ kubectl delete namespace test

namespace "test" deleted


ЗАВДАННЯ (JOB). Створимо одноразове завдання, задачу, що рахуватиме з 9 до 1:

$ vim job.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: countdown
spec:
  template:
    metadata:
      name: countdown
    spec:
      containers:
      - name: counter
        image: centos:7
        command:
          - "bin/bash"
          - "-c"
          - "for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done"
      restartPolicy: Never

$ kubectl apply -f job.yaml


Перевіримо статус її створення:

$ kubectl get jobs

NAME        DESIRED   SUCCESSFUL   AGE
countdown   1         1            6s

$ kubectl get pods

NAME                 READY     STATUS      RESTARTS   AGE
countdown-lc80g      0/1       Completed   0          16s

Після виконання завдання відповідний под зупиниться. Щоб дізнатись більше про даний под можна також виконати наступну команду:

$ kubectl describe jobs countdown

Name:   countdown
Namespace:  default
Selector: controller-uid=35701d78-a73f-11e7-8ef0-080027d6708d
Labels:   controller-uid=35701d78-a73f-11e7-8ef0-080027d6708d
    job-name=countdown
Annotations:  ...
Parallelism:  1
Completions:  1
Start Time: Mon, 02 Oct 2017 02:59:17 -0400
Pods Statuses:  0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels: controller-uid=35701d78-a73f-11e7-8ef0-080027d6708d
    job-name=countdown
  Containers:
   counter:
    Image:  centos:7
    Port: <none>
    Command:
      bin/bash
      -c
      for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done
    Environment:  <none>
    Mounts:   <none>
  Volumes:    <none>
Events:
  FirstSeen LastSeen  Count From    SubObjectPath Type    Reason      Message
  --------- --------  ----- ----    ------------- --------  ------      -------
  1m    1m    1 job-controller      Normal    SuccessfulCreate  Created pod: countdown-qwbgd


Чи переглянути журнал поду:

$ kubectl logs countdown-qwbgd

9
8
7
6
5
4
3
2
1


Видалення для задачі, як і для всіх інших об'єктів, відбувається наступним чином:

$ kubectl delete job countdown

job "countdown" deleted


Цей об'єкт Kubernetes корисний у випадках одноразових операцій на кшталт міграції баз, запуску одноразових перевірок і т.п. Також існують багаторазові завдання, з якими детальніше можна ознайомитись за посиланням https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/


INGRESS. Примітив Kubernetes, котрий відповідає за переадресацію запитів на сервіси (примітиви Kubernetes) по певним правилам (в залежності від імені хосту і/або URL-ів). Контроллер для нього необхідно виливати окремо і вже після цього можна користуватись новим об'єктом kind: Ingress. Тобто, можна сказати, що це і плагін і об'єкт одночасно. Як його активувати, та як ним користуватись я вже описав тут https://blog.ipeacocks.info/2018/01/kubernetes-part-v-configure-and-use.html

Наступні пункти не являються об'єктами Kubernetes, але про них також хотілося б згадати.

Журнал (Logs). Трішки ще хотілося б додати про логи. Створимо тестовий под pod-logme, контейнер в якому виконує команди для генерації виводу stdout та stderr:

$ vim pod-logme.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-logme
spec:
  containers:
  - name: gen
    image: centos:7
    command:
      - "bin/bash"
      - "-c"
      - "while true; do echo $(date) | tee /dev/stderr; sleep 1; done"

$ kubectl apply -f pod-logme.yaml


Переглянемо логи контейнеру gen в цьому поді:

$ kubectl logs --tail=5 pod-logme -c gen

Mon Oct 2 21:30:36 UTC 2017
Mon Oct 2 21:30:37 UTC 2017
Mon Oct 2 21:30:37 UTC 2017
Mon Oct 2 21:30:38 UTC 2017
Mon Oct 2 21:30:38 UTC 2017


Окрім цього можна спостерігати за потоком всіх повідомлень (щось на зразок tail -f), що з'являються з часом:

$ kubectl logs -f --since=10s pod-logme -c gen

...
Mon Oct 2 21:31:35 UTC 2017
Mon Oct 2 21:31:36 UTC 2017
Mon Oct 2 21:31:36 UTC 2017
Mon Oct 2 21:31:37 UTC 2017
Mon Oct 2 21:31:37 UTC 2017
^C


Параметр --since=10s вказує на те, що повідомлення почнуть виводитись лише за останні 10 секунд. Інакше ж на початку будуть виведені всі можливі логи доступні в stdout контейнера. Можна також вивести лише 5 останніх повідомлень:

$ kubectl logs --tail=5 pod-logme -c gen

Mon Oct 2 22:07:26 UTC 2017
Mon Oct 2 22:07:27 UTC 2017
Mon Oct 2 22:07:27 UTC 2017
Mon Oct 2 22:07:28 UTC 2017
Mon Oct 2 22:07:28 UTC 2017


Логи можна виводити навіть для контейнерів, котрі вже закінчили свій життєвий цикл:

$ vim pod-oneshot.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-oneshot
spec:
  containers:
  - name: gen
    image: centos:7
    command:
      - "bin/bash"
      - "-c"
      - "for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done"

$ kubectl apply -f pod-oneshot.yaml


Контейнер цього поду лише виведе цифри від 9 до 1 і вже потім закінчить свою роботу:

$ kubectl logs pod-oneshot -c gen

9
8
7
6
5
4
3
2
1


Secrets. Окрім цього Kubernetes володіє функціоналом збереження секретних даних на кшталт паролів, API-ключів і т.п. Проте перед використанням цього механізму варто прийняти до уваги наступні властивості:
  • Секрети доступні всім об'єктам у межах неймспейсу, в якому вони були створені
  • Доступ до них відбувається через змінні середовища чи том (volume) контейнера в поді
  • Секрети на вузлах зберігаються на tmpfs томах
  • На кожен секрет виділяється не більше 1МБ
  • API сервер Kubernetes зберігає секрети як незашифрований текст в etcd
Для демонстрації механізму створимо секрет apikey:

$ echo -n "A19fh68B001j" > ./apikey.txt

$ kubectl create secret generic apikey --from-file=./apikey.txt

secret "apikey" created

$ kubectl get secrets

NAME                  TYPE                                  DATA      AGE
apikey                Opaque                                1         22s
default-token-k016d   kubernetes.io/service-account-token   3         71d

$ kubectl describe secret apikey
Name:           apikey
Namespace:      default
Labels:         <none>
Annotations:    <none>

Type:   Opaque

Data
====
apikey.txt:     12 bytes


Тепер створимо под з томом, на котрому буде знаходитись секрет apikey.txt:

$ vim pod-secret.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-secret
spec:
  containers:
  - name: shell
    image: centos:7
    command:
      - "bin/bash"
      - "-c"
      - "sleep 10000"
    volumeMounts:
      - name: apikeyvol
        mountPath: "/tmp/apikey"
        readOnly: true
  volumes:
  - name: apikeyvol
    secret:
      secretName: apikey

$ kubectl apply -f pod-secret.yaml


Перевіримо чи справді секрет знаходиться в контейнері поду:

$ kubectl exec pod-secret -c shell -i -t -- bash

[root@pod-secret /]# mount | grep apikey
tmpfs on /tmp/apikey type tmpfs (ro,relatime)
[root@pod-secret /]# cat /tmp/apikey/apikey.txt
A19fh68B001j


Проте, виходячи з останньої особливості збереження секретів в Kubernetes, доступ до etcd варто надавати лише вузькому колу людей.


Вузол (node). Вузли, на кшталт об'єктів Kubernetes, можуть бути опитані на предмет стану роботи та опцій з якими вони працюють. Як і у всіх попередніх випадках за це відповідають функції get та describe:

$ kubectl get nodes

NAME      STATUS    AGE       VERSION
k8s-m1    Ready     71d       v1.7.1
k8s-s1    Ready     71d       v1.7.1
k8s-s2    Ready     71d       v1.7.1
k8s-s3    Ready     71d       v1.7.1

$ kubectl describe node k8s-s1

Name:     k8s-s1
Role:
Labels:     beta.kubernetes.io/arch=amd64
      beta.kubernetes.io/os=linux
      kubernetes.io/hostname=k8s-s1
      shouldrun=here
Annotations:    node.alpha.kubernetes.io/ttl=0
      volumes.kubernetes.io/controller-managed-attach-detach=true
Taints:     <none>
CreationTimestamp:  Sat, 22 Jul 2017 15:31:29 -0400
Conditions:
  Type      Status  LastHeartbeatTime     LastTransitionTime      Reason        Message
  ----      ------  -----------------     ------------------      ------        -------
  OutOfDisk     False   Mon, 02 Oct 2017 19:02:36 -0400   Mon, 04 Sep 2017 16:13:03 -0400   KubeletHasSufficientDisk  kubelet has sufficient disk space available
...
  Ready     True  Mon, 02 Oct 2017 19:02:36 -0400   Mon, 02 Oct 2017 17:03:27 -0400   KubeletReady      kubelet is posting ready status. AppArmor enabled
Addresses:
  InternalIP: 192.168.60.111
  Hostname: k8s-s1
Capacity:
 cpu:   1
 memory:  1495332Ki
 pods:    110
Allocatable:
 cpu:   1
 memory:  1392932Ki
 pods:    110
System Info:
 Machine ID:      b8240bf4076b4a2f88f491b9bd176fd9
 System UUID:     20EA61E7-5746-42F3-B1AC-174DC0EF4DD4
 Boot ID:     de304d90-37fd-4589-9eed-1cae280f6db2
 Kernel Version:    4.4.0-83-generic
 OS Image:      Ubuntu 16.04.2 LTS
 Operating System:    linux
 Architecture:      amd64
 Container Runtime Version: docker://1.12.0
 Kubelet Version:   v1.7.1
 Kube-Proxy Version:    v1.7.1
ExternalID:     k8s-s1
Non-terminated Pods:    (8 in total)
  Namespace     Name            CPU Requests  CPU Limits  Memory Requests Memory Limits
  ---------     ----            ------------  ----------  --------------- -------------
  ingress     default-backend-2856460360-wktts    10m (1%)10m (1%)  20Mi (1%) 20Mi (1%)
...
  kube-system     weave-scope-agent-b6zhk       0 (0%)  0 (0%)    0 (0%)    0 (0%)
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  CPU Requests  CPU Limits  Memory Requests Memory Limits
  ------------  ----------  --------------- -------------
  290m (28%)  10m (1%)  130Mi (9%)  190Mi (13%)
Events:   <none>


Як бачимо, тут є купа корисної сервісної інформації на кшталт версій підсистем, завантаженості вузла контейнерами і т.п.

Посилання:
http://kubernetesbyexample.com/
https://kubernetesbootcamp.github.io/kubernetes-bootcamp/
https://kubernetes.io/docs/concepts/configuration/overview/
https://habrahabr.ru/company/flant/blog/332990/
https://habrahabr.ru/company/flant/blog/333956/
https://www.xenonstack.com/blog/deploying-python-application-on-docker-kubernetes
https://blog.yadutaf.fr/2017/07/28/tracing-a-packet-journey-using-linux-tracepoints-perf-ebpf/
https://kubernetes.io/docs/concepts/configuration/overview/

Немає коментарів:

Дописати коментар