пятница, 14 апреля 2017 г.

Kubernetes

Kubernates - опенсорсная система от Google для автоматического деплоя, масштабирования и управления контейнизированных приложений. Общее название для таких систем это оркестратор, хоть это одна из функций этой системы.
Дальше будут использоваться примеры из курса на Udemy Edward Viaene, который я просмотрел.

Чтобы попробовать локально Kubernates в деле, можно воспользоваться Minikube, который является элементарной реализацией Kubernates, создает одну VM (VirtualBox, кстати он должен быть установлен, чтобы минька заработала), которая будет единственным узлом (на продакшине нужно иметь минимум 3 узла, один из которых хозяйский).

minikube start - запуск
minikube dashboard - открыть браузер с Web UI урлом
minikube dashboard --url - вывести в консоль урл, а не открывать вкладку в браузере


Архитектура

Kubernates кластер состоит из:
- Хозяина - который координирует кластер;
- узлы - работники, которые выполняют приложения.

Хозяин(Master)
- планирует выполнение приложений,
- поддерживает приложения в желаемом состоянии,
- масштабирует приложения при необходимости,
- выкатывает обновления.

Узел(Node) 

- это виртуальная или физическая машина, которая выполняет функцию воркера.
На каждом узле запущен агент, называется Kubelet, который управляет нодой и общается с хозяйской-нодой. На ноде также должен быть инструмент, который может выполнять контейнерные операции, такой как Docker или rkt. В контейнерах происходит запуск запланированных Подов.

Pod 

- группа совместно размещенных контейнеров и томов на узле Kubernates кластера. Pod может состоять из одного или группы связанных контейнеров, которые и составляют целостное приложение.
В Kubernates используется текстовый файл(в формате yaml или json) для описания и запуска сущностей, одной из таких сущностей является Под, файл по формату и смыслу напоминает docker-compose файл.
Для примера pod-helloworld.yml:

apiVersion: v1
kind: Pod
metadata:
  name: helloworld.example.info
  labels:
    app: helloworld
spec:
  containers:
  - name: k8s-demo
    image: wardviaene/k8s-demo
    ports:
    - containerPort: 3000

Kubelet

- как уже сказано, запущенный агент на узле, который отвечает за запуск и работающие поды на узле, для этого он подключается к хозяйскому узлу, чтобы узнать что запустить, что поддерживать, не поменялись ли условия.

Kube-proxy 

- еще один агент, работающий на узлах, у него задача обеспечивать сетевое соединение между подами, которым это разрешено. В пределах пода контейнеры все связаны через порты, они могу обращаться к друг другу через localhost:PORT_NUM. А вот контейнры между подами как на одном узле, так и на разных связываются через виртуальные сети. Так kube-proxy вносит коректировку в iptables, каждой ноды, обеспечивая сеть между теми подами, которые настроены через хозяйскую ноду в одни сети.

Service 

- в контексте Kubernates, это агенты которые подключаются к iptables узлов кластера и работают как распределители нагрузки(открытый вопрос запущены ли эти агенты физически на хозяйском узле или нет). Внешний мир по сети подключается именно к службам, а уже они перераспределяют трафик по Kubernates кластеру. Как я понимаю, службы могут отправить запрос на узел, где реально нет пода, который может его обработать, потому что балансировка происходит по принципу равномерности, но в iptables есть запись куда перенаправлять такие запросы, и проблема решается на уровней фаейрволов.
Поды должны быть всегда под службами и не создаваться напрямую, потому что они динамичны и по необходимости могут переносится по кластеру. Поэтому службы можно видеть как логические мосты между "смертными" подами и другими службами или конечными пользователями.
Создание службы означает создание endpoint-а для наших пода/ов. Их типы:
  • ClusterIP - создается виртуальный IP адрес, доступный только в пределах кластера( используется по-умолчанию )
  • NodePort - открывается один и тот же порт на всех нодах, и он доступен снаружи
  • LoadBalancer - обращается к поставщику облака, чтобы он создал службу распределителя нагрузки и перенаправлял трафик снаружи на порт NodePort узлов. В случае AWS это ELB. Этот тип используется в производственной среде(продакшине).
Кроме виртуальных айпишников можно включить DNS добавку и тогда сервису присваивается через поле ExternalName доменное имя, а не адрес.

Сервис можно создать либо через командную строку 
$ kubectl expose ...
Либо из YAML-файла
apiVersion: v1
kind: Service
metadata:
  name: helloworld-service
spec:
  ports:
  - port: 31001
    nodePort: 31001
    targetPort: nodejs-port
    protocol: TCP
  selector:
    app: helloworld
  type: NodePort
В этом случае мы явно указываем порты, поэтому ответственность за занятость порта лежит на нас. По умолчанию сервис можно открывать только на диапазоне портов 30000-32767, но на хозяйском узле через аргумент утилиты kube-apiserver мы можем изменить этот диапазон (init скрипты).

Replication controller 

- инструмент для масштабирования, он настраивается, через указание сколько одновременно реплицируемых подов должно работать постоянно, он следит если какой-то отваливается, то подымает до нужного количества реплик снова. Этот инструмент рекомендуется использовать даже если нужно держать только один под, это гарантирует, что под будет постоянно работать -- несмотря на его падения от непредсказуемых ошибок, случайного удаления через kubectrl, он будет снова запускаться репликейшин контроллером.

apiVersion: v1
kind: ReplicationController
metadata:
  name: helloworld-controller
spec:
  replicas: 2
  selector:
    app: helloworld # для какого именно приложения создается эти ReplicationController
  template: # в шаблоне находится определение Пода, который будет реплицироваться
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
      - name: k8s-demo
        image: wardviaene/k8s-demo
        ports:
        - containerPort: 3000

Replica Set 

- это следующее поколение после Replication controller. Его улучшение - новый селектор, который умеет делать выборку подов через фильтрирование по набору значений, предшественник мог только точно сравнивать.

Deployment object 

- использует именно Replica Set, а не Replication controller. Он делает выкатывание и обновление приложения.
Определяется состояние, которое нужно достичь, чтобы считать выкатку/обновление законченным, и Kubernates проверяет соответствует ли указанному текущее состояние кластера.
Возможности:
- создание выкатки/деплоймента;
- обновление выкатки к новой версии;
- выкатка обновления(обновление без прекращения работы приложения)
- откат к предыдущей версии;
 - приостановить и продолжить выкатку (выкатиться только до определенной части).
Пример:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: helloworld-deployment
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
      - name: k8s-demo
        image: wardviaene/k8s-demo
        ports:
        - containerPort: 3000

По этому файлу Replica Set и соответствующие Pods будут созданы тоже.

Именно Deployment выставляется наружу созданием службы, которая и предоставляет его миру. Если это делать вручную, а не из файла, то это так
$ kubectl expose deployment hello-minikube --type=NodePort

Labels

Ярлыки это пары ключ/значение, которые могут быть прикреплены к объектам. Это как теги в AWS или других поставщиков облачных решений, служащие для пометки ресурсов.
Помечать можно разные объекты, например можно поды, чтобы понимать, какие отделения предприятия работающие поды обслуживают и к какой среде относятся.
environment: dev/sta/qa/prod
department: engineering/finance/marketing
Label Selectors помогают фильтровать ресурсы, например определенные ноды могут использоваться только для подов среды QA/DEV, другие же только PROD.

$ kubectl label nodes node1 hardware=high-spec
$ kubectl label nodes node2 hardware=low-spec
И потом
apiVersion: v1
kind: Pod
...
spec:
  ...
  nodeSelector:
    hardware: high-spec

Annotations

Это атрибуты на сущности в Kubernates, которые как и Labels являются ключами/значениями, но по ним инструментарием Kubernates вычурную фильтрацию не произведешь. Они служат хранилищем для метаданных, которые могут быть использованы такими клиентами как расширения, библиотеки. Там можно хранить версии образов, тегов в VCS, урлы стредств логирования, наблюдения, аналитики, к которым подключен ресурс, и тому подобное.

Health checks

Периодическая проверка исправности - это одно из условий нахождения приложений за распределителем нагрузки. Есть два пути: планировщиком периодически запускать скрипт в контейнере, который будет сам уведомлять балансер о своей исправности; или с балансера периодически дергать урлу из контейнера.

apiVersion: v1
kind: Pod
...
spec:
  containers:
  ...
    livenessProbe:
      httpGet:
        path: /
        port: nodejs-port
      initialDelaySeconds: 15
      timeoutSeconds: 30

Secrets

Этот инструмент платформы обеспечивает поставку данных доступа, ключей, паролей и любых других тайных данных в поды. Его использует и сам Kubernates для доступа к своему внутреннему API.
Стратегии получения тайных данных:
 - переменные среды;
- файлы;
- внешние образы контейнеров с такими данными, которые попадают в список зависимостей файла сборки образа.

Создание пары имя пользователя и пароль загрузкой из локальных файлов(хозяйская машина где запускается утилита kubectl)
$ kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt
secret "db-user-pass" created
Заливка ключа и сертификата
$ kubectl create secret generic ssl-certificate --from-file=ssh-privatekey=~/.ssh/id_rsa --ssl-сert=mysslcert.crt
Можно пойти и по пути создания секретов с файлов:

Шаг 1. Обезопасим себя от хранения спецсимволов в US-ASCII средах
$ echo -n "root" | base64
cm9vdA==
$ echo -n "password" | base64
cGFzc3dvcmQ=

Сам файл secrets-db-secret.yml:
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  username: cm9vdA==
  password: cGFzc3dvcmQ=

Само создание секрета с файла:
$ kubectl create -f secrets-db-secret.yml
secret "db-secret" created
$
Теперь этот файл можно хранить в системе контроля версий.

Теперь варианты использования таких ключей.

Переменные среды создаются так:
apiVersion: v1
kind: Pod
...
spec:
  containers:
  ...
    env:
     - name: SECRET_USERNAME
       valueFrom:
         secretKeyRef:
           name: db-secret
           key: username
     - name: SECRET_PASSWORD
     ...

Если же с файла, то так:
apiVersion: v1
kind: Pod
...
spec:
  containers:
  ...
    volumeMounts:
    - name: credvolume
      mountPath: /etc/creds
      readOnly: true
  volumes:
  - name: credvolume
    secret: 
      secretName: db-secrets

Создается диск, создается на нем файлы и содержимое созданного секрета выливается в эти файлы.

ConfigMap

Инструмент для хранения неуязвимых с точки зрения безопасности параметров. Как и secret это пары ключ-значение. Приложения могут их считывать с 
- переменных среды;
- как аргументы командной строки контейнера в настройках Пода;
- из файлов используя тама Volumes.
Через файлы это делать вообще удобно, так мы можем изменяя в подключенном томе конфигурационный файл, который замаунчен в месте, где ожидает работающее приложение, например, nginx, не изменяя образ с которого запущен контейнер, влиять на работающий сервис без сложностей обновления образа в реджистри и перезапуска сервиса.
$ cat <<EOF > app.properties
driver=jdbc
database=postgres
lookandfeel=1
otherparams=xyz
param.with.hierarchy=xyz
EOF
$ kubectl create configmap app-config --from-file=app.properties

Теперь это использовать так:
apiVersion: v1
kind: Pod
...
spec:
  containers:
  ...
    volumeMounts:
    - name: config-volume
      mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: app-config

Специфика в том, что под каждый параметр в файле app.properties создается отдельный файл:
/etc/config/driver, /etc/config/param/with/hierarchy - что немного дико для меня...

Если переменные среды то:
apiVersion: v1
kind: Pod
...
spec:
  containers:
  ...
    env:
      - name: DRIVER
        valueFrom:
          configMapKeyRef:
            name: app-config
            key: driver
      -name: DATABASE
      [...]
Все с "дикостью" становится понятно после того, как создаем конфигурацию с настройками nginx:
$ cat reverseproxy.conf
server {
    listen       80;
    server_name  localhost;

    location / {
        proxy_bind 127.0.0.1;
        proxy_pass http://127.0.0.1:3000;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

$ kubectl create configmap nginx-config --from-file=reverseproxy.conf
configmap "nginx-config" created
$ kubectl get configmap nginx-config -o yaml
apiVersion: v1
data:
  reverseproxy.conf: |
    server {
        listen       80;
        server_name  localhost;

        location / {
            proxy_bind 127.0.0.1;
            proxy_pass http://127.0.0.1:3000;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
    }
kind: ConfigMap
metadata:
  creationTimestamp: 2017-04-12T10:47:52Z
  name: nginx-config
  namespace: default
  resourceVersion: "163143"
  selfLink: /api/v1/namespaces/default/configmaps/nginx-config
  uid: 7a36e484-1f6d-11e7-b4f6-080027acf412
Как видно конфиг вошел под именем файла как ключ-значение. При этом значением является все содержимое файла, похоже что "дикая" участь ждет только файлы *.properties, в других форматах файлы попадут просто целым файлом.

$ cat nginx.yml
apiVersion: v1
kind: Pod
metadata: name: helloworld-nginx labels: app: helloworld-nginx spec: containers: - name: nginx image: nginx:1.11 ports: - containerPort: 80 volumeMounts: - name: config-volume mountPath: /etc/nginx/conf.d - name: k8s-demo image: wardviaene/k8s-demo ports: - containerPort: 3000 volumes: - name: config-volume configMap: name: nginx-config items: - key: reverseproxy.conf path: reverseproxy.conf $ kubectl create -f nginx.yml
Если обратить внимание то volume config-volume создается из конфигмапы и, что файл берется из ключа reverseproxy.conf (а мы заметили, что мапа была создана как ключ значение с таким именем, а значение - содержимое файла), path определяет и имя файла и его путь на создаваемом диске. 
$ cat nginx-service.yml
apiVersion: v1
kind: Service
metadata:
  name: helloworld-nginx-service
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    app: helloworld-nginx
  type: NodePort
$ kubectl create -f nginx-service.yml
service "helloworld-nginx-service" created
$ minikube service helloworld-nginx-service --url
http://192.168.99.100:31123
И вот сервис в работе.

Еще один момент, чтобы войти в контейнер, если в одном поде больше одного контейнера, кроме указания пода, нужно указать и контейнер, в который нужно входить. Что немного отличается от привычного exec в docker:
$ kubectl exec -i -t helloworld-nginx -c nginx -- bash

Ingress

Решение в пределах платформы с версии Kubernetes 1.1, что обеспечивает входящие подключения в кластер. Это альтернатива для внешнего распределителя нагрузок.
С его помощью запускается собственный ingress controller(по сути лоудбалансер) в кластере. Есть уже готовый от Kubernetes такой контролер или же можно написать свой собственный балансировщик нагрузки.
Готовый контроллер состоит из сервиса Ingress Controller, который будет шлюзом в кластер, он запущен в контексте Пода, на котором запущен один контейнер с именем nginx ingress controller, это и есть готовое решение от Kubernetes. Этот nginx и будет распределять нагрузку по кластеру, правила он получает из Ingress Object.
Определяются правила через файл типа Ingress, который на момент прохождения курса был еще в бете.
apiVersion: extention/v1beta1
kind: Ingress
metadata:
  name: helloworld-les
spec:
  rules:
  - host: helloworld-v1.example.com #если запрос приходит на такое доменное имя
    http: #делать переправку http forward 
      paths: #послольку среди путей указан только один, коренной, то все запросы обслуживаются одним сервисом
      - path: / 
        backend:
          serviceName: helloworld-v1 #этим
          servicePort: 80
  - host: helloworld-v2.example.com #если запрос приходит на такое доменное имя
    http: #дальше все как в первых коментариях
      paths: 
      - path: / 
        backend:
          serviceName: helloworld-v2 
          servicePort: 80
   

Дальше нужно создать в принципе контролер или репликасет, в этом случае у нас будет контролер.

nginx-ingress-controller.yml:
apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx-ingress-controller
  labels:
    k8s-app: nginx-ingress-lb
spec:
  replicas: 1
  selector:
    k8s-app: nginx-ingress-lb
  template:
    metadata:
      labels:
        k8s-app: nginx-ingress-lb
        name: nginx-ingress-lb
    spec:
      ...
      containers:
      - image: gcr.io/google_containers/nginx-ingress-controller:0.8.3
        name: nginx-ingress-lb
        ...
        env:
          ...
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
        args:
        - /nginx-ingress-controller
        - --default-backend-service=$(POD_NAMESPACE)/echoheaders-default

Видно, что аргументы будут переданы в контейнер от Google, которые запускают указанное приложение nginx-ingress-controller с сервисом по умолчанию, который будет обслуживать запросы не подходящие не под одно правило из файла правил описанного выше.
И полный кластер приложения будет представлен 5 файлами, первые два мы описали. А дальше еще будет:
- echoservice.yml - в нем будет описано две сущности ReplicationController и его Service, которые и будут обеспечивать сервис по умолчанию.
- helloworld-v1.yml - Deployment и его Service, которые обеспечат сервис для обслуживания одного правила
- helloworld-v2.yml - Deployment и его Service, которые обеспечат сервис для обслуживания второго правила
Полный код здесь

Volumes

Инструмент для Stateful приложений. Есть несколько плагинов:
- Local Volume
- AWS Cloud (EBS Storage)
- Google Cloud (Google Disk)
- Network storage (NFS, Cephfs)
- Azure Disk

Задумка дочистить реализацию Volumes до состояния, чтобы можно было бы в контейнер с MySQL подключить такой диск и базы бы гарантировано надежно сохранялись на диске(NFS работает хорошо с малыми файлами, которым может быть много, но не с большими файлами). Пока на прод это выкачивать стремно.
Пример EBS Storage:
...
spec:
  containers:
  - name: k8s-demo
    ...
    volumeMounts:
    - mountPath: /myvol
      name: myvolume
    ...
  volumes:
  - name: myvolume
    awsElasticBlockStore:
      volumeID: vol-23423423423423423423

kubectl drain - подготовить ноду к закрытию
kubectl drain --force - если приложения в подах не созданы с возможностью мягкого закрытия, то нужно валить силой
Чтобы не расходовать деньги на неиспользуемые ресурсы, нужно удалять ненужные диски и ненужные поды:
$ aws ec2 delete-volume --volume-id vol-23423423423423423423
$ kubectl delete -f helloworld-with-volume.yml

Networking

Подход к работе сети очень отличается от Docker установки по умолчанию.
- Контейнер с контейнером в пределах пода - через localhost:portNum
- Под с сервисом - с использованием NodePort и DNS
- внешнее с сервисом - с использованием LoadBalancer и NodePort
- под с подом - поды могут находиться на разных узлах физической сети, поэтому каждый из них должен быть маршрутизируемым, имея свой IP адрес и подвязанный реальный. Эта маршрутизация реализуется в зависимости от настроенной сети. В случае AWS Virtual Private Network (VPC). Мастер Kubernetes выделяет 254 адреса на ноду, такие подсети добавляются в таблицу маршрутизации VPC. AWS позволяет объединять в кластер не больше 50 узлов, можно сделать запрос и увеличить до 100, но быстродействие пострадает.
Google Compute Engine и Azure тоже предлагают VPC реализации, но многие другие облачные решения - нет. С ними помогут альтернативы:
- реализующие CNI(Container Network Interface) - Calico, Weave.
- Overlay Network - Flannel.

DaemonSet

Живет на хозяине кластера, запускает указанные поды до всех остальных на новоподключеной ноде к кластеру, дальше его задача обеспечивать работающими указанные поды на всех нодах кластера. Задача этих под обеспечить работу димонов логирования, кластерного хранилища, мониторинга и так далее.

Job

Эта служба переодически запускает в работу несколько подов и по успешному завершению ложит их, и может записывать какие-то результаты после этого.

Web UI

Поставка Kubernates идет с Web UI, которую можно использовать вместо утилиты командной строки kubectl
Возможности:
- посмотреть, что сейчас запущено на кластере;
- создание и изменение отдельных ресурсов(resources) и загрузок (workloads) - аналогично kubectl create and delete;
- получить информацию про состояние ресурсов - аналогично (like kubectl describe pod)

Находится панель по адресу: https:///ui
Если ее нет на мастере кластера, то можно установить вручную:

$ kubectl create -f https://rawgit.com/kubernetes/dashboard/master/src/deploy/kubernetes-dashboard.yaml
Если запрашивается пароль, то его можно получить
$ kubectl config view

Addons

Дополнения находятся в директории /etc/kubernetes/addons хозяина кластера.
Одна из дополнений DNS служба, которая позволяет приложениям обращаться друг другу в пределах кластера, где айпи адреса могут изменяться.
Чтобы DNS сервис заработал, нужно определить службу(Service definition).
Сервисы получают имена определяющиеся пространством имен(Namespace).
Namespace - помогают сервисы разделить логически в пределах кластера, существует пространство имен default и мы можем добавить свои. Это виртуальные кластеры в пределах физического кластера.
Создается несколько доменных имен на сервис: имя сервиса, с пространством имен, и полным именем.
$ host app2-service
app2-service has address 10.0.0.2
$ host app2-service.default
app2-service.default has address 10.0.0.2
$ host app2-service.default.svc.cluster.local
app2-service.default.svc.cluster.local has address 10.0.0.2

svc - это сокращенно от service
cluster.local - это домен, на котором размещается кластер. Домен с таким именем как в примере создает Minikube.

Есть неймспейс в котором работают системные поды/сервисы самого Kubernates. Имя этого пространства имен kube-system. Например в нем поднята пода kube-dns, в которой работает контейнер с DNS Server, а он представлен/балансируется под сервисом с таким же именем как пода kube-dns.
Например на моей локалке это выглядит так:
$ kubectl get pod --namespace kube-system
NAME                          READY     STATUS    RESTARTS   AGE
kube-addon-manager-minikube   1/1       Running   1          8d
kube-dns-v20-gd9sg            3/3       Running   3          8d
kubernetes-dashboard-qm47v    1/1       Running   1          8d

Чтобы увидеть адрес DNS Server из контейнера в кластере:
# cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.0.0.10
options ndots:5


Для AWS используется kops.

Kubernates можно запускать на всех широкоизвестных облачных поставщиках. И под каждый из них написан отдельный инструмент.  До этого был устаревший инструмент kube-up.sh.

Чтобы установить на хозяйскую ноду kops: 1) скачиваем уже скомпилированный бинарник последнего/нужного релиза и устанавливаем в утилиты OS
$ wget https://github.com/kubernetes/kops/releases/download/1.5.3/kops-linux-amd64
$ chmod +x kops-linux-amd64
$ sudo mv kops-linux-amd64 /usr/local/bin/kops

2) Устанавливаем awscli
$ sudo apt-get install python-pip
$ LC_ALL=C sudo pip install awscli

Дальше идем в web-console AWS и создаем там пользователя. Видим его ключ и пароль. И потом с хозяйской ноды подсоединяемся к AWS облаку.

$ aws configure
AWS Access Key ID [None]: BKICJETCV7E6T1K277CC
AWS Secret Access Key [None]: 9TfAeLhiP+GFGUdDytYkrxDTE3GEC3a92vlA\aLN

$ ls -ahl  ~/.aws/
total 16K
drwxrwxr-x 2 ubuntu ubuntu 4.0K Apr  4 12:09 .
drwxr-xr-x 5 ubuntu ubuntu 4.0K Apr  4 12:09 ..
-rw------- 1 ubuntu ubuntu   10 Apr  4 12:09 config
-rw------- 1 ubuntu ubuntu  116 Apr  4 12:09 credentials

Комментариев нет:

Отправить комментарий