Translate

пʼятниця, 6 жовтня 2017 р.

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 shell -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.


Набір реплік (ReplicaSets). Рівень абстракції над подами, котрий можна створити наступним чином:

$ vim replicaset-x.yaml

apiVersion: extensions/v1beta1
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

Як я вже згадував, також часто використовують Replication Controller, котрий, по суті, являється тим же, що і ReplicaSet. Різниця лиш в тому, що останній володіє більш прогресивним синтаксисом указання міток, завдяки опції matchExpressions та, на відміну від першого, не веде історії оновлень подів. Офіційна документація наразі не рекомендує використання Replication Controller-ів.

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


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

$ vim deployment-09.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: deployment-09
spec:
  replicas: 2
  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: extensions/v1beta1
kind: Deployment
metadata:
  name: deployment-09
spec:
  replicas: 2
  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-правила доступу до кінцевих подів.

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

$ vim deployment-for-service.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: deployment-for-service
spec:
  replicas: 1
  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

Та сервіс із іменем simpleservice:

$ vim service-for-pods.yaml

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

$ 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 трафік до адреси 172.30.228.255: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.

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

$ vim deploy-disc.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: deploy-disc
spec:
  replicas: 1
  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). Це директорія, що доступна для всіх контейнерів одного поду. По-замовчуванню ж кожен контейнер володіє окремим дисковим простором, котрий у разі передеплою буде створено заново. Типів томів існує велика множина, тому я продемонструю роботу лише одного типу - emptyDir. Для цього створимо под pod-volume із двох контейнерів:

$ vim pod-volume.yml

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.yml

$ 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 --show-all
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. Тобто, можна сказати, що це і плагін і об'єкт одночасно. Як його активувати, та як ним користуватись я опишу у наступній статті, адже це достатньо довга історія.

Наступні пункти не являються об'єктами 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/

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

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