Kubernetes practice
December 22, 2022

Деплой инфраструктуры по модели gitops

Данная статья является описанием прохождения мной одноимённого курса на Практикуме. Курс хорошо составлен и продуман.

Перед началом прохождения данного курса необходимо выполнить некоторую подготовку.

  1. Зарегистрировать свой домен;
  2. Делегировать управление доменом Яндексу;
  3. Создать сеть для работы в облаке;
  4. Создать SA для кластера k8s;
  5. Создать ключ KMS для кластера k8s;
  6. Создать необходимые для работы переменные;
  7. Зарезервировать статический адрес для домена;
  8. Прописать необходимые записи в Cloud DNS;
  9. Выпустить для данного домена сертификат;
  10. Настроить SG для работы кластера k8s;
  11. Создать кластер k8s.
  12. Добавить из Marketplace k8s несколько дополнений.

Итак, поехали!

Регистрируем свой домен.

Зарегистрируем свой домен на сайте https://www.freenom.com/ru/index.html?lang=ru Домен здесь можно зарегистрировать совершенно бесплатно. На сайте необходимо зарегистрироваться и добавить в корзину домен/домены.
Я буду использовать для выполнения работ домен cameda.ga.

Делегируем управление доменом Яндекс Облако.

Для делегирования домена в Яндекс необходимо прописать на сайте freenom NS записи облака.

Services-->My Domains-->Manage Domain (кнопка напротив выбранного домена)-->Management Tools-->Nameservers-->Use custom nameservers (enter below)

И вставляем адреса DNS серверов Яндекс Облака:

  • ns1.yandexcloud.net.
  • ns2.yandexcloud.net.

Сеть для облака создали здесь:
https://teletype.in/@cameda/ZbTkF70KWK9

SA для работы с кластером k8s создали здесь: https://teletype.in/@cameda/w93lPMPQWS4

Ключ KMS для шифрования секретов в кластере k8s создали здесь:
https://teletype.in/@cameda/vBIMDOZi7vV

Неоходимые для работы переменные.

export FOLDER=$(yc config get folder-id)
export ZONE=ru-central1-a
export NETWORK=$(yc vpc network get cameda-test --format json | jq -r '.id')
export SUBNET=$(yc vpc subnet get subnet-a --format json | jq -r '.id')
export SA=$(yc iam service-account get cameda-service --format json | jq -r '.id')
export KMS=$(yc kms symmetric-key get k8s-key --format json | jq -r ".id")

Зарезервируем статический адрес для домена в подсети.

yc vpc address create \
   --folder-id $FOLDER \
   --name deploy_gitops \
   --description "deploy gitops model" \
   --labels test=deploy_gitops \
   --external-ipv4 zone=$ZONE \
   --async
export IP=$(yc vpc address get deploy_gitops --format=json | jq -r ".external_ipv4_address" | jq -r ".address")

Пропишем необходимые записи в Cloud DNS.

1. Но для начала надо создать публичную зону.

yc dns zone create \
  --folder-id $FOLDER \
  --name k8s-ingress \
  --description "deploy gitops model" \
  --zone cameda.ga. \
  --public-visibility \
  --async

2. Создадим А запись для домена. В качестве IP адреса будем использовать созданный ранее адрес.

yc dns zone add-records --name k8s-ingress \
--record "*.infra.cameda.ga. 600 A $IP"

3. Проверим, что всё правильно резолвит.

host test.infra.cameda.ga.
test.infra.cameda.ga has address 158.160.37.229

Создадим сертификат.

yc certificate-manager certificate request \
  --folder-id $FOLDER \
  --name kube-infra \
  --domains "*.infra.cameda.ga" \
  --challenge dns
export CERT=$(yc cm certificate get kube-infra --format=json | jq -r ".id")

Добавим CNAME запись в домен для прохождения валидации.

yc dns zone add-records --name k8s-ingress --record \
"_acme-challenge.infra.cameda.ga. 600 CNAME $CERT.cm.yandexcloud.net."

Выпуск Wildcard сертификата может занять несколько часов!!!

Создадим правила SG для корректной работы кластера k8s.

yc vpc sg create --name k8s-sg \
"--rule" "description=access all egress port,direction=egress,from-port=1,to-port=65535,protocol=any,v4-cidrs=[0.0.0.0/0]" \
"--rule" "description=access 22 port,direction=ingress,port=22,protocol=tcp,v4-cidrs=[0.0.0.0/0]" \
"--rule" "description=access 443 port,direction=ingress,port=443,protocol=tcp,v4-cidrs=[0.0.0.0/0]" \
"--rule" "description=access 6443 port,direction=ingress,port=6443,protocol=tcp,v4-cidrs=[0.0.0.0/0]" \
"--rule" "description=balancer port,direction=ingress,from-port=1,to-port=65535,protocol=tcp,v4-cidrs=[198.18.235.0/24],v4-cidrs=[198.18.248.0/24]" \
"--rule" "description=service port,direction=ingress,from-port=30000,to-port=32767,protocol=tcp,v4-cidrs=[0.0.0.0/0]" \
"--rule" "description=ICMP,direction=ingress,protocol=icmp,v4-cidrs=[10.0.0.0/8],v4-cidrs=[192.168.0.0/16]" \
"--rule" "description=PODtoService,direction=ingress,from-port=1,to-port=65535,protocol=any,v4-cidrs=[10.95.0.0/16],v4-cidrs=[10.96.0.0/16]" \
"--rule" "description=Self,direction=ingress,from-port=1,to-port=65535,protocol=any,predefined=self_security_group" \
--network-id $NETWORK --description "k8s access" --folder-id $FOLDER --async

Здесь мы указываем сидры для работы подов: [10.97.0.0/16],v4-cidrs=[10.98.0.0/16]
При создании кластера укажем такие же.

export SG=$(yc vpc sg get k8s-sg --format json | jq -r '.id')

Создадим кластер k8s с CNI Calico, KMS ключем для шифрования данных и фиксированной нод группой. Версия кластера 1.21. Без обновлений.

yc k8s cluster create \
--folder-id $FOLDER \
--name cam-praktikum \
--description "cameda praktikum" \
--network-id $NETWORK \
--zone $ZONE \
--subnet-id $SUBNET \
--public-ip \
--release-channel stable \
--version 1.21 \
--cluster-ipv4-range 10.95.0.0/16 \
--service-ipv4-range 10.96.0.0/16 \
--auto-upgrade=false \
--security-group-ids $SG \
--enable-network-policy \
--node-service-account-id $SA \
--service-account-id $SA \
--kms-key-id $KMS \
--daily-maintenance-window start=22:00,duration=10h \
--async

Важно! Создание кластера может занимать до часа. После того как кластер будет создан, создаём нод группу.

Важно! Копируем публичный ключ id_rsa.pub в файл /Users/cameda/ssh-pairs.txt.

Создание динамической нод группы для кластера.

yc k8s node-group create \
--folder-id $FOLDER \
--name cam-praktikum-group \
--cluster-name cam-praktikum \
--description "praktikum" \
--metadata serial-port-enable=1 \
--metadata-from-file=ssh-keys=/Users/cameda/ssh-pairs.txt \
--location zone=$ZONE \
--platform standard-v3 \
--memory 8 \
--cores 4 \
--core-fraction 100 \
--disk-type network-ssd \
--disk-size 96 \
--network-acceleration-type standard \
--network-interface security-group-ids=$SG,subnets=$SUBNET,ipv4-address=nat \
--version 1.21 \
--container-runtime containerd \
--auto-scale min=0,max=2,initial=1 \
--auto-upgrade \
--auto-repair \
--max-expansion 1 \
--max-unavailable 1 \
--daily-maintenance-window start=22:00,duration=5h \
--allowed-unsafe-sysctls net.ipv4.tcp_timestamps \
--async

Важно! Создание нод группы также может занимать до часа.

Добавляем доп. приложения из Merketplace.

  1. NodeLocal DNS. По мануалу: https://teletype.in/@cameda/1GKt9Af0tBR
    Или https://cloud.yandex.ru/docs/managed-kubernetes/operations/applications/node-local-dns#marketplace-install
  2. ALB контроллер: https://teletype.in/@cameda/3n75CiuRGB-
    Или https://cloud.yandex.ru/docs/managed-kubernetes/operations/applications/alb-ingress-controller#install-alb-marketplace

Заключительная часть подготовительного этапа.

Установим kubectl по мануалу: https://kubernetes.io/ru/docs/tasks/tools/install-kubectl/

И подключимся к кластеру.

yc managed-kubernetes cluster get-credentials --id Cluster_ID --external

Готово! Подготовка закончилась.

Создадим приложение в Deployment и получим к нему доступ извне.

kubectl create namespace httpbin

Добавим следующую спецификацию.

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
  namespace: httpbin
  labels:
    app: httpbin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
  template:
    metadata:
      labels:
        app: httpbin
    spec:
      containers:
        - name: httpbin
          image: kennethreitz/httpbin:latest
          ports:
            - name: http
              containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  namespace: httpbin
spec:
  type: NodePort
  selector:
    app: httpbin
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
      nodePort: 30081
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: httpbin
  namespace: httpbin
  annotations:
    ingress.alb.yc.io/subnets: e9b16nenl5a52h6o9djb
    ingress.alb.yc.io/external-ipv4-address: 158.160.37.229
    ingress.alb.yc.io/group-name: infra-alb
    ingress.alb.yc.io/security-groups: enpauibqifvmvss6balo
spec:
  rules:
    - host: httpbin.infra.cameda.ga
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: httpbin
                port:
                  number: 80
EOF

При применении данного манифеста будет создан балансировщик на базе ALB. Это мжет занять минут 10-15. Далее добавим поддержку TLS.

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: httpbin
  namespace: httpbin
  annotations:
    ingress.alb.yc.io/subnets: e9b16nenl5a52h6o9djb
    ingress.alb.yc.io/external-ipv4-address: 158.160.37.229
    ingress.alb.yc.io/group-name: infra-alb
    ingress.alb.yc.io/security-groups: enpauibqifvmvss6balo
spec:
  tls: 
    - hosts: 
        - "httpbin.infra.cameda.ga" 
      secretName: yc-certmgr-cert-id-fpqepq5cbt3q7548a2nj
  rules:
    - host: httpbin.infra.cameda.ga
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: httpbin
                port:
                  number: 80
EOF

При применении данного манифеста может не добавиться http роутер в балансировщик. В этом случае его необходимо вручную модифицировать обработчик. Надо прописать в одном из двух обработчиков поддержку HTTPS, сертификат и роутер с поддержкой TLS. После этого httpbin.infra.cameda.ga должен будет открыться.

Также ещё сталкивался с тем, что при применении манифеста не создался балансировщик. Я просто удалил ingress и создал его заново.

Важно! Сначала применяем ingress без TLS, потом с ним. Это уменьшает риск ошибок.

curl -I httpbin.infra.cameda.ga
HTTP/1.1 301 Moved Permanently
location: https://httpbin.infra.cameda.ga:443/
date: Thu, 17 Nov 2022 17:01:23 GMT
server: ycalb
transfer-encoding: chunked

Установка GitLab, GitLab Runner и интеграция их с k8s.

1. Установить GitLab можно только через UI облака. Это можно сделать по инструкции:
https://cloud.yandex.ru/docs/managed-gitlab/operations/instance/instance-create

2. После создания подключимся к инстансу:
https://cloud.yandex.ru/docs/managed-gitlab/operations/connect

3. Создаём проект в GitLab.

4. Settings→CI/CD→Runners->Expand. Здесь нам потребуются URL, registration token.

5. Установим GitLab Runner в кластер k8s.
В поле "Имя домена GitLab" вставляем URL нашего GitLab, полученный на 4 этапе.
В поле "Регистрационный токен" вставляем token нашего GitLab, полученный на 4 этапе.
В поле "Пространство имён" выбираем kube-system.

6. Repository->Files. Создаём новый файл (+->New File). Вставляем имя - .gitlab-ci.yml

И код пайплайна:

stages:
  - echo

echo job:
  stage: echo
  script:
    - echo "Hello world!"

Применяем изменения. Commit changes. Для этого мотаем экран вниз.

7. CI/CD->Pipelines. Смотрим статус нашего пацплайна. Если passed, то всё корректно отработало.
Проверим. Нажимаем на passed->Jobs->passed.

8. Создадим две директории в меню File: charts, values. Также создадим ещё два файла:

8.1 .gitignore — игнорируемые Git файлы
8.2 commands.sh — файл для сохранения команд, которые вводим вручную, необходимых для развертывания

Готово!

Развернём ArgoCD.

export HELM_EXPERIMENTAL_OCI=1 
helm pull oci://cr.yandex/yc-marketplace/yandex-cloud/argo/chart/argo-cd \ --untar --untardir=charts --version=4.5.3-1
helm install -n argocd \
  --create-namespace \
  argocd charts/argo-cd
kubectl port-forward svc/argo-cd-argocd-server -n kube-system 8080:443

После установки заходим в приложение: http://localhost:8080
Логин: admin
Password получаем с помощью команды:

kubectl -n kube-system get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

Готово!

Настраиваем связку GitLab+ArgoCD.

Заходим в GitLab. Settings->Access Tokens. Ввоим данные:

  1. Имя токена (argo-cd);
  2. Если не указывать срок жизни токена, то он выпустится на бессрочной основе;
  3. Роль - Developer;
  4. Scopes - read_repository.

Create Project Access Token.

touch values/argocd.yaml
vi values/argocd.yaml
configs:
  repositories:
    infra:
      password: <AccessToken>
      project: default
      type: git
      url: https://cameda3.gitlab.yandexcloud.net/gitlab_test/infra.git
      username: gitlab-ci-token

<AccessToken> - находится в профиле пользователя в разделе AccessToken.

helm -n argocd upgrade --install \
    argocd \
    charts/argo-cd \
    -f values/argocd.yaml

Проверяем, что апгрейд применился:

kubectl -n argocd get secret argocd-repo-infra
NAME                TYPE     DATA   AGE
argocd-repo-infra   Opaque   5      4m34s

Да, всё ОК. Раз секрет есть, значит ОК.

Управление секретами в Git репозитории.

Чтобы скрывать пароли из Git-репозитория, подключим инструмент Helm Secrets. Он позволяет зашифровывать чувствительную информацию с помощью различных методов и расшифровывать при применении.

helm plugin install https://github.com/jkroepke/helm-secrets --version v3.12.0
brew install age

Установка golang, Sops.

golang:

{apt,yum,brew} install golang
echo 'export GOPATH=~/go' >> ~/.bashrc
source ~/.bashrc
mkdir $GOPATH/src/go.mozilla.org/sops/

SOPS:

git clone https://github.com/mozilla/sops.git $GOPATH/src/go.mozilla.org/sops/
cd $GOPATH/src/go.mozilla.org/sops/
git checkout develop
make install

Генерируем сертификат.

age-keygen -o key.txt

Шифруем манифест.

helm secrets enc values/argocd.yaml

Важно! У меня на macOS Sops не встал.