Files
netology-devops/src/graduate_work/readme.md
2023-04-01 12:55:19 +07:00

20 KiB
Raw Blame History

Дипломная работа

Выполнение дипломной работы курса netology DevOps инженер. Оригинал задания доступен по ссылке.

Весь код, выполненный по ходу выполнения работы находится в репозиториях на github:

Создание облачной инфраструктуры

Задание.

Предварительная настройка

Данный параграф описывает выполнения шагов 1-3 из задания.

Предварительная настройка включает в себя несколько шагов, необходимых для последующей работы с yandex.cloud через terraform. Данные шаги выполняются в ручную, но могут быть автоматизированы, например, через ansible.

  1. Установить утилиту yc и подключится к облаку.

  2. Создание сервисного аккаунта с ролью editor на дефолтной директории облака:

    yc iam service-account create --name terraform-acc
    yc resource-manager folder add-access-binding --name default --role editor --subject "serviceAccount:<accId>"
    

    где <accId> - это уникальный идентификатор нового сервисного аккаунта. Затем нужно получить ключ доступа для данного сервисного аккаунта:

    yc iam access-key create --service-account-name terraform-acc --format=json
    
  3. Создание s3-bucket для хранения состояния terraform

    yc storage bucket create --name=dnc-netology-tf-state
    

Следующий шаг - инициализация terraform и создание нового workspace. Для инициализации используется команда:

terraform init \
  -backend-config="bucket=dnc-netology-tf-state" \
  -backend-config="access_key=<service_account_key_id>" \
  -backend-config="secret_key=<service_account_secret_key>"

где <service_account_key_id> и <service_account_secret_key> данные полученные на шаге получения ключа доступа для сервисного аккаунта.

Создание и переключение на новый workspace с названием prod:

terraform workspace new prod

Для упрощения процесса был создан ansible-playbook terraform_init.yml. Чтобы усилить безопасность некоторые переменные были зашифрованы через ansible-vault. Таким образом, для запуска достаточно выполнить команду

ansible-playbook --ask-vault-pass -i ansible/terraform_init terraform_init.yml

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

Создание VPC и подсетей через terraform

Для создания VPC и двух подсетей будет использована следующая конфигурация:

resource "yandex_vpc_network" "netology-gw-network" {
  name = "netology-gw-network"
}

resource "yandex_vpc_subnet" "netology-gw-subnet-a" {
  name           = "netology-gw-subnet-a"
  zone           = "ru-central1-a"
  network_id     = yandex_vpc_network.netology-gw-network.id
  v4_cidr_blocks = ["192.168.10.0/24"]
}

resource "yandex_vpc_subnet" "netology-gw-subnet-b" {
  name           = "netology-gw-subnet-b"
  zone           = "ru-central1-b"
  network_id     = yandex_vpc_network.netology-gw-network.id
  v4_cidr_blocks = ["192.168.15.0/24"]
}

Затем нужно последовательно выполнить команды для проверки применения конфигурации в облаке (выполняется из директории terraform):

terraform plan
terraform apply
terraform destroy

Создание Kubernetes кластера

Задание.

Конфигурация машин будет одинаковая, поэтому terraform-конфигурация будет выглядеть следующим образом:

resource "random_shuffle" "netology-gw-subnet-random" {
  input        = [yandex_vpc_subnet.netology-gw-subnet-a.id, yandex_vpc_subnet.netology-gw-subnet-b.id]
  result_count = 1
}

resource "yandex_compute_instance" "k8s-cluster" {
  for_each = toset(["control", "node01", "node2"])

  name = each.key

  resources {
    cores  = 2
    memory = 2
  }

  boot_disk {
    initialize_params {
      image_id = "fd8kdq6d0p8sij7h5qe3" # ubuntu-20-04-lts-v20220822
      size = "20"
    }
  }

  network_interface {
    subnet_id = random_shuffle.netology-gw-subnet-random.result[0]
    nat       = true
  }

  metadata = {
    ssh-keys = "ubuntu:${file("~/.ssh/id_rsa.pub")}"
  }
}

output "cluster_ips" {
  value = {
    internal = values(yandex_compute_instance.k8s-cluster)[*].network_interface.0.ip_address
    external = values(yandex_compute_instance.k8s-cluster)[*].network_interface.0.nat_ip_address
  }
}

Для распределения по разным зонам доступности использован ресурс random_shuffle.

После деплоя инфраструктуры необходимо скачать репозиторий kubespray, сформировать inventory-директорию, содержащую сам inventory.ini с данными о виртуальных машинах и group_vars.

После данного шага достаточно запустить ansible-playbook cluster.yml с переданным inventory:

ansible-playbook -i ansible/kubespray/inventory.ini vendor/kubespray/cluster.yml

Когда установка кластера закончится необходимо с control-node взять файл /etc/kubernetes/admin.conf, положить его локально по пути ~/.kube/conf и изменить ip-адрес кластера на ip-адрес самой control-node. Этого будет достаточно, чтобы подключится к кластеру через утилиту kubectl.

kubectl get pods --all-namespaces
NAMESPACE     NAME                                       READY   STATUS    RESTARTS      AGE
kube-system   calico-kube-controllers-7f679c5d6f-kfmkz   1/1     Running   0             49m
kube-system   calico-node-8v2d9                          1/1     Running   0             50m
kube-system   calico-node-rrbcv                          1/1     Running   0             50m
kube-system   calico-node-w67gl                          1/1     Running   0             50m
kube-system   coredns-5867d9544c-7n4qz                   1/1     Running   0             47m
kube-system   coredns-5867d9544c-rfbxs                   1/1     Running   0             47m
kube-system   dns-autoscaler-59b8867c86-2rqdd            1/1     Running   0             47m
<...>

Создание тестового приложения

Задание.

Для данного задания будет использован репозиторий тестового приложения в котором расположено небольшое тестовое приложение на JS. Данное приложение запаковывается в образ с nginx. Dockerfile расположен внутри репозитория (file)

Docker-образ доступен на Docker Hub.


Подготовка системы мониторинга и деплой приложения

Задание.

Перед деплоем необходимо было активировать nginx-ingress-controller в конфигурации kubespray. Для этого в файле ansible/kubespray/group_vars/k8s_cluster/addons.yml изменено значение по ключам ingress_nginx_enabled и ingress_nginx_host_network на true.

Для деплоя всех необходимых сервисов было создано 2 helm-чарта и использован готовый helm-чарт:

Применение изменений производится командами helm:

  • helm install - первый деплой чарта
  • helm upgrade - повторный деплой чарта для применения изменений
  • helm upgrade -i - установка или обновление чарта

Конкретные команды, которые были выполнены:

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install monitoring prometheus-community/kube-prometheus-stack -f k8s/helm/kube-prometheus-stack/values.yml
helm install simple-app k8s/helm/simple-app
helm install --set "config.github.user=<access_token>" --set "config.github.token=<token_secret>" --set "config.github.secret=<webhook_secret>" atlantis k8s/helm/atlantis

где <access_token>, <token_secret> - это данные персонального access-токена, созданного на github, а <webhook_secret> - строка, которая должна совпадать в конфигурации webhook и atlantis.

После выполнения сервисы стали доступны по следующим доменам:

  • http://grafana-gw.my.to - grafana (логин admin, пароль prom-operator)
  • http://app-gw.my.to - приложение
  • http://atlantis-gw.my.to/ - atlantis

UPD atlantis был подключён к репозиторию, но не получается корректно настроить backend для terraform. Особенно вызывает проблемы необходимость каждый день выпускать новый токен для сервисного аккаунта yandex.cloud.


Установка и настройка CI/CD

Задание.

В качестве сервиса автоматизации сборки и развёртывания приложения был выбран jenkins. Для его деплоя создан helm-чарт k8s/helm/jenkins, деплой которого производится стандартно:

helm install --set "docker.dockerHubUser=<dockerHubUser>" --set "docker.dockerHubPassword=<dockerHubPassword>" jenkins k8s/helm/jenkins

где <dockerHubUser> и <dockerHubPassword> - данные авторизации в hub.docker.com для возможности пушить образы в регистри.

После установки jenkins будет доступен по ip-адресу любой рабочей node кластера по пути /jenkins (например http://84.201.172.95/jenkins). Далее необходима первоначальная настройка сервиса в которую входят:

  • авторизация в качестве начального администратора. Ключ доступа можно посмотреть в логах pod, например, командой:

    kubectl --namespace ci-cd logs jenkins-production-main-0
    
  • установка первоначальных плагинов (можно выбрать рекомендованный вариант).

  • создание дополнительного пользователя (можно пропустить).

  • изменение конфигурации безопасности Host Key Verification Strategy на Accept first connection

  • установка дополнительных плагинов:

    • Kubernetes для возможности запускать jenkins-воркеры внутри k8s-кластера
    • Generic Webhook Trigger для возможности более гибко настраивать поведение скриптов jenkins на github-webhooks.
  • выставить значение 0 в настройке Количество процессов-исполнителей для мастер-ноды.

  • в Configure Clouds добавить новую конфигурацию kubernetes. Адрес кластера и сертификат можно взять из локальной конфигурации kubectl.

После данных действий останется создать два проекта с типом pipeline:

  • для сборки образов при каждом изменении кода в ветках. Скрипт для этого проекта находится в файле jenkins/ref.jenkinsfile
  • для сборки образов и деплое изменений при создании нового git-тэга. Скрипт для этого проекта находится в файле jenkins/tag.jenkinsfile

Плагин Generic Webhook Trigger, который используется внутри данных скриптов, требует, чтобы сборка каждого проекта была запущена хотя бы раз перед фактическим использованием. Это необходимо для применения конфигурации переменных для проекта.

Последним шагом будет настройка двух github-webhook в репозитории приложения Dannecron/parcel-example-neko. Webhook для первого приложения должен инициироваться при каждом push в репозиторий (Just the push event.). Webhook для второго приложения должен инициироваться только при создании тэга (Branch or tag creation, создание веток будет отфильтровано).

По такой логике были созданы следующие теги в регистри

  • feature-1 - при создании новой git-ветки в репозитории.

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

    helm list --selector "name=simple-app"
    kubectl describe pod --show-events=false simple-app-production-application-7c777968c6-cndh2 | grep Image
    
    NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART            APP VERSION
    simple-app      default         3               2023-04-01 04:48:20.999406345 +0000 UTC deployed        simple-app-0.1.0 latest
    
    Image:          dannecron/parcel-example-neko:0.1.0
    

Данные, необходимые для сдачи задания

  1. Репозиторий с конфигурационными файлами Terraform: Dannecron/netology-devops-gw-infra директория terraform;
  2. Пример pull request с комментариями созданными atlantis'ом: github;
  3. Репозиторий с конфигурацией ansible, если был выбран способ создания Kubernetes кластера при помощи ansible: Dannecron/netology-devops-gw-infra директория ansible/kubespray;
  4. Репозиторий с Dockerfile тестового приложения и ссылка на собранный docker image:
  5. Репозиторий с конфигурацией Kubernetes кластера: Dannecron/netology-devops-gw-infra директория k8s;
  6. Ссылка на тестовое приложение и веб интерфейс Grafana с данными доступа:
    • тестовое приложение: http://app-gw.my.to
    • web-интерфейс grafana: http://grafana-gw.my.to (логин admin, пароль prom-operator)

Допущения

Небольшой список допущений, которые были сделаны во время выполнения работы:

  • Все ноды имеют внешний ip-адрес. В реальном проекте стоило сделать Bastion host для доступа по ssh до всех остальных нод, а так же перенаправления трафика к кластеру.
  • Создано несколько ansible-playbook под разные задачи. Возможно, стоило объединить всё в один playbook с подключением разных ролей.
  • Helm-чарт для деплоя приложения никуда не опубликован, что усложняет работу с ним. Таким образом, в дальнейшем стоит настроить хотя бы публикацию версии (архива) в качестве артефактов в релизах github.