В этой статье будет следующее:
- Заведение DNS домена на reg.ru.
- Управление DNS зоной в Yandex DNS c помощью terraform.
- Создание Kubernetes в Yandex Cloud.
- Резервируем внешний статический IP адрес.
- Установка Jenkins c помощью terraform модуля helm_release.
- Создание ClusterIssue(Issue) для создания letsencypt сертификата.
Быстрая установка Jenkins с помощью terraform в Kubernetes в Yandex Cloud с letsencypt.
Скачать репозиторий:
git clone https://github.com/patsevanton/infrastructure-as-a-code-example.git
Перейти в каталог terraform-helm-release-jenkins:
cd terraform-helm-release-jenkins
Заполнить private.auto.tfvars на базе шаблона private.auto.tfvars.example.
Запустить установку:
k8s_install.sh
Разберем подробнее.
Заводим DNS домен на reg.ru — mycompany.ru или mycompany.org.ru. В настройках домена на вкладке «DNS-серверы и управление зоной» указываем DNS Yandex Cloud:
- ns1.yandexcloud.net
- ns2.yandexcloud.net
DNS зона управляется с помощью terraform кода. Документация — https://cloud.yandex.ru/docs/dns/.
resource "yandex_dns_zone" "dns_domain" { name = replace(var.dns_domain, ".", "-") zone = join("", [var.dns_domain, "."]) public = true }
Так как у zone на конце должна быть точка, то используем метод join для соединения нашего домена и точки.
Создание DNS записи.
В имени DNS записи на конце необходима точка, поэтому используем метод join для соединения DNS записи и точки. Код:
resource "yandex_dns_recordset" "jenkins_dns_domain" { zone_id = yandex_dns_zone.dns_domain.id name = join("", [var.jenkins_dns_name, "."]) type = "A" ttl = 200 data = [yandex_vpc_address.addr.external_ipv4_address[0].address] }
Поле data — список IP адресов, на которые должны резолвиться эта DNS запись.
*Создание сервисного аккаунта.
Перед созданием Kubernetes кластера необходимо рассказать о создание сервисного аккаунта. Название сервисного аккаунта — sa-k8s-admin.
resource "yandex_iam_service_account" "sa-k8s-admin" { folder_id = var.yc_folder_id name = "sa-k8s-admin" } resource "yandex_resourcemanager_folder_iam_member" "sa-k8s-admin-permissions" { folder_id = var.yc_folder_id role = "admin" member = "serviceAccount:${yandex_iam_service_account.sa-k8s-admin.id}" }
Лучше использовать yandex_resourcemanager_folder_iam_member вместо yandex_resourcemanager_folder_iam_binding, так как не всегда работает корректно. У меня на другом коде вызывает ошибку. Issue — https://github.com/yandex-cloud/terraform-provider-yandex/issues/267 и https://github.com/yandex-cloud/terraform-provider-yandex/issues/283.
"Binding for role "mdb.dataproc.agent" not found in policy"
В role указываем какие права имеет сервисный аккаунт. Более подробно в документации https://cloud.yandex.ru/docs/iam/concepts/access-control/roles.
Создание kubernetes кластера в Yandex Cloud.
Для создание kubernetes кластера в Yandex Cloud используется уже обычный terraform код:
resource "yandex_kubernetes_cluster" "zonal_k8s_cluster" { name = "my-cluster" description = "my-cluster description" network_id = yandex_vpc_network.k8s-network.id master { version = "1.21" zonal { zone = yandex_vpc_subnet.k8s-subnet.zone subnet_id = yandex_vpc_subnet.k8s-subnet.id } public_ip = true } service_account_id = yandex_iam_service_account.sa-k8s-admin.id node_service_account_id = yandex_iam_service_account.sa-k8s-admin.id release_channel = "STABLE" // to keep permissions of service account on destroy // until cluster will be destroyed depends_on = [yandex_resourcemanager_folder_iam_member.sa-k8s-admin-permissions] } # yandex_kubernetes_node_group resource "yandex_kubernetes_node_group" "k8s_node_group" { cluster_id = yandex_kubernetes_cluster.zonal_k8s_cluster.id name = "name" description = "description" version = "1.21" labels = { "key" = "value" } instance_template { platform_id = "standard-v3" network_interface { nat = true subnet_ids = [yandex_vpc_subnet.k8s-subnet.id] } resources { cores = 2 memory = 4 core_fraction = 50 } boot_disk { type = "network-hdd" size = 32 } scheduling_policy { preemptible = true } metadata = { ssh-keys = "ubuntu:${file("~/.ssh/id_rsa.pub")}" } } scale_policy { fixed_scale { size = 1 } } allocation_policy { location { zone = "ru-central1-b" } } maintenance_policy { auto_upgrade = true auto_repair = true maintenance_window { day = "monday" start_time = "15:00" duration = "3h" } maintenance_window { day = "friday" start_time = "10:00" duration = "4h30m" } } } locals { kubeconfig = <<KUBECONFIG apiVersion: v1 clusters: - cluster: server: ${yandex_kubernetes_cluster.zonal_k8s_cluster.master[0].external_v4_endpoint} certificate-authority-data: ${base64encode(yandex_kubernetes_cluster.zonal_k8s_cluster.master[0].cluster_ca_certificate)} name: kubernetes contexts: - context: cluster: kubernetes user: yc name: ycmk8s current-context: ycmk8s users: - name: yc user: exec: apiVersion: client.authentication.k8s.io/v1beta1 command: yc args: - k8s - create-token KUBECONFIG } output "kubeconfig" { value = local.kubeconfig }
Если кластер тестовый, то можно снизить стоимость используя 50% ядра и использовать прерываемые виртуальные машины.
resources { ... core_fraction = 50 } scheduling_policy { preemptible = true }
Сеть и внешний статический IP адрес.
Указываем сеть:
resource "yandex_vpc_network" "k8s-network" { name = "k8s-network" }
Указываем подсеть:
resource "yandex_vpc_subnet" "k8s-subnet" { zone = "ru-central1-b" network_id = yandex_vpc_network.k8s-network.id v4_cidr_blocks = ["10.5.0.0/24"] depends_on = [ yandex_vpc_network.k8s-network, ] }
Указываем внешний статический IP адрес:
resource "yandex_vpc_address" "addr" { name = "static-ip" external_ipv4_address { zone_id = "ru-central1-b" } }
Jenkins установливаем c помощью terraform модуля helm_release.
Рассмотрим helm_release.tf:
provider "helm" описывает настройки подключения к kubernetes для helm.
provider "helm" { kubernetes { host = yandex_kubernetes_cluster.zonal_k8s_cluster.master[0].external_v4_endpoint cluster_ca_certificate = yandex_kubernetes_cluster.zonal_k8s_cluster.master[0].cluster_ca_certificate exec { api_version = "client.authentication.k8s.io/v1beta1" args = ["k8s", "create-token"] command = "yc" } } }
Создаем ingress-nginx.
resource "helm_release" "ingress_nginx" { name = "ingress-nginx" repository = "https://kubernetes.github.io/ingress-nginx" chart = "ingress-nginx" version = "4.2.1" wait = true depends_on = [ yandex_kubernetes_node_group.k8s_node_group ] set { name = "controller.service.loadBalancerIP" value = yandex_vpc_address.addr.external_ipv4_address[0].address } }
В terraform опции --set key=value передаются так:
set { name = "controller.service.loadBalancerIP" value = yandex_vpc_address.addr.external_ipv4_address[0].address }
В данном случае для controller.service.loadBalancerIP указываем внешний статический IP адрес.
Устанавливаем cert-manager по аналогии с ingress-nginx.
resource "helm_release" "cert-manager" { namespace = "cert-manager" create_namespace = true name = "jetstack" repository = "https://charts.jetstack.io" chart = "cert-manager" version = "v1.9.1" wait = true depends_on = [ yandex_kubernetes_node_group.k8s_node_group ] set { name = "installCRDs" value = true } }
Устанавливаем jenkins.
resource "helm_release" "jenkins" { namespace = "jenkins" create_namespace = true name = "jenkins" repository = "https://charts.jenkins.io" chart = "jenkins" wait = true version = "4.1.17" depends_on = [ yandex_kubernetes_node_group.k8s_node_group ] values = [ #file("${path.module}/jenkins-values-google-login.yaml") yamlencode(local.jenkins_values_google_login) ] }
Jenkins helm чарту на вход необходимо указать value.yaml с нашими настройками.
Есть 2 способа указать value.yaml.
Первый способ использовать готовый и настроеный value.yaml в текущей директории:
values = [ file("${path.module}/jenkins-values-google-login.yaml") ]
Второй способ использовать yamlencode для перевода terraform кода в yaml код.
values = [ yamlencode(local.jenkins_values_google_login) ]
Как получить terraform код из yaml кода?
Подготавливаете и настраиваете value.yaml, а затем используя вот такую команду переводите в terraform код:
echo 'yamldecode(file("value.yaml"))' | terraform console
В моем случае value.yaml имеет другое название:
echo 'yamldecode(file("jenkins-values-google-login.yaml"))' | terraform console
Я использую yamlencode, потому что внутри yamlencode кода ссылаюсь на terraform переменные, а так же потому что job лежат отдельно.
Полученный код вставляете в local.jenkins_values_google_login
locals { jenkins_values_google_login = { сюда } }
Затем вместо ваших данных:
"hostName" = jenkins.mycompany.ru
используйте terraform переменные
"hostName" = "${var.jenkins_dns_name}"
Рассмотрим local.jenkins_values_google_login.
В корне стоит controller от которого идут все настройки.
Для настройка jenkins из кода используются jcasc, job-dsl. Примеры jcasc https://github.com/jenkinsci/configuration-as-code-plugin
"JCasC" = { "authorizationStrategy" = <<-EOT loggedInUsersCanDoAnything: allowAnonymousRead: false EOT
Более подробно — https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/demos/global-matrix-auth/README.md
В configScripts каждый блок EOT это отдельный файл в контейнере jenkins. systemMessage системное сообщение.
"configScripts" = { "jenkins-configuration" = <<-EOT jenkins: systemMessage: This Jenkins is configured and managed 'as code' by Managed Cloud team. EOT
Указываем файл job:
"job-config" = yamlencode({ jobs = [ { script = file("${path.module}/job1.groovy") }, { script = file("${path.module}/job2.groovy") } ] })
Указываем список view:
jenkins: views: - all: name: "all" - list: columns: - "status" - "weather" - "jobName" - "lastSuccess" - "lastFailure" - "lastDuration" - "buildButton" jobNames: - "job1" name: "stage" - list: columns: - "status" - "weather" - "jobName" - "lastSuccess" - "lastFailure" - "lastDuration" - "buildButton" jobNames: - "job2" name: "test" viewsTabBar: "standard"
Указываем как мы будет входить в jenkins:
"securityRealm" = <<-EOT googleOAuth2: clientId: "${var.clientId}" clientSecret: "${var.clientSecret}" domain: "${var.google_domain}" EOT
Я использую googleOAuth2. Можно еще использовать local, ldap и другие.
Дополнительные плагины jenkins:
"additionalPlugins" = [ "google-login:1.6", "job-dsl:1.81", "allure-jenkins-plugin:2.30.2", "ws-cleanup:0.42", "build-timeout:1.21", "timestamper:1.18", "google-storage-plugin:1.5.6", "permissive-script-security:0.7", "ansicolor:1.0.2", "google-oauth-plugin:1.0.6", ]
Настройка Ingress:
"ingress" = { "annotations" = { "cert-manager.io/cluster-issuer" = "letsencrypt-prod" } "apiVersion" = "networking.k8s.io/v1" "enabled" = true "hostName" = "${var.jenkins_dns_name}" "ingressClassName" = "nginx" "tls" = [ { "hosts" = [ "${var.jenkins_dns_name}", ] "secretName" = "jenkins-tls" }, ] }
Если у вас нет letsencrypt, то вы удаляете cert-manager.io/cluster-issuer.
Указываем javaOpts чтобы запускать Job-DSL скрипты:
javaOpts: '-Dpermissive-script-security.enabled=true'
Сравнение настроенный yaml и yamlencode из terraform кода.
Чем плох настроенный yaml в качестве value.yaml для helm чарта относительно yamlencode ?
- Он длинный.
- В yaml коде нельзя вынести кусок кода (например job) в отдельный файл.
- Для формирования заполненого настроенного value.yaml необходимо использовать templatefile. Мне кажется это лишнее.
В JCasC.configScripts каждый блок до вертикальной черты (|) будет сохранен как отдельный файл в контейнере Jenkins.
В Yaml формате все job нужно расписывать. Если сравнить:
job-config: | jobs: - script: > pipelineJob('job1') { logRotator(120, -1, 1, -1) authenticationToken('secret') definition { cps { script("""\ pipeline { agent any parameters { string(name: 'Variable', defaultValue: '', description: 'Variable', trim: true) } options { timestamps() ansiColor('xterm') timeout(time: 10, unit: 'MINUTES') } stages { stage ('build') { steps { cleanWs() echo "hello job1" } } } }""".stripIndent()) sandbox() } } }
и чтение файла job, то вынос файла лучшее красивее и читабельнее:
"job-config" = yamlencode({ jobs = [ { script = file("${path.module}/job1.groovy") }, { script = file("${path.module}/job2.groovy") } ] })
Добавление kind: ClusterIssuer в Kubernetes.
Если мы будем добавлять kind: ClusterIssuer как resource "kubernetes_manifest" и добавим как подключатся к Kubernetes используя provider "kubernetes", то получим ошибку:
cannot create REST client: no client config
Мы не можем развернуть resource "kubernetes_manifest" в котором ссылаемся на другой ресрус — https://github.com/hashicorp/terraform-provider-kubernetes/issues/1380
Поэтому создадим файл ClusterIssuer.yaml.tpl и будем формировать его через templatefile передав всего 1 переменную email_letsencrypt.
Вот мы с вами и прошли то, что напланировали в самом начале.
ссылка на оригинал статьи https://habr.com/ru/post/683844/
Добавить комментарий