背景
GitOpsというキーワードが出てきており、職場でもGitOpsを使った検証環境の運用が始まっている。
しかし、GitOps環境を構築するにあたってIaCは進めているものの、まだ人手によるOperationがなくなっていないのも事実です。
そこで、Azure上にkubernetes clusterの構築からGitOpsの仕組みを導入するまでを自動化する仕組みとして
Bedrockを検証したので手順や気づきについてまとめていきます。
GitOpsとは
Weaveworks社が提唱した,Kubernetesの運用ベストプラクティス
詳しくはGitOpsを参照
記事はボリュームがあるので 要点をまとめると下記のような感じです。
- システムの状態がGit管理されている
- コードとして宣言的なインフラストラクチャの実現 ⇔ Kubernetesも宣言的な仕組み
- Git上で承認された場合は、環境にも適用される
- Git = 環境を保証し、差異があったらアラートする
GitOpsを実現するために、 現在はFluxというOSSを使ってkubernetes clusterの状態とgit管理しているmanifestの間で 同期を取る仕組みを実現している。
fluxについて過去にチュートリアル記事を書いたので参考になればと思います。 cabi99.hatenablog.com
現在はFluxを使って下図の環境を構築しています。 この環境をpipeline以外、すべて自動で構築します。
Bedrockとは
Microsoftが提供するGitOpsワークフローを使用してKubernetesクラスターを運用可能にするterraformテンプレートです。 GitOpsワークフローは、Fabrikateの定義を中心に展開されており、構成から構成を分離する、より高い抽象化レベルで展開を指定できます。 今回の検証ではBedrockを使ってAKSクラスターを自動構築することを優先したのでFabrikateについては検証しません。
手順
前提条件
helm: v2.13.1 → 3系だとまだ動かないっぽい
terraform: v0.12.20 → テンプレートは古いバージョン対応だったのでupgradeコマンドでスクリプトを更新しました
Azure CLI
$ az version { "azure-cli": "2.0.80", "azure-cli-command-modules-nspkg": "2.0.3", "azure-cli-core": "2.0.80", "azure-cli-nspkg": "3.0.4", "azure-cli-telemetry": "1.0.4", "extensions": {} }
おおまかな流れ
bedrockのテンプレートで実行している内容を簡単に書くと以下になります。
- azure cli でresource groupを作成する
- terraformでvnet,subnetを作成する
- terraformでを作成する
- terraformでaks clusterを作成する
- terraformのnull_resourceを使ってshellでflux agentをインストール/起動する
詳細
1.azure cli でresource groupを作成する
az group create -n gitOpsCluster -l japaneast
2.vnetとsubnetをterraformで作成する ※利用するfileはbedrockの以下path
/bedrock/cluster/azure/vnet
参考までにmain.tf variables.tfはresource_group_name以外、ほぼデフォルト値
resource "azurerm_virtual_network" "vnet" { name = var.vnet_name location = var.location address_space = [var.address_space] resource_group_name = var.resource_group_name dns_servers = var.dns_servers tags = var.tags } resource "azurerm_subnet" "subnet" { count = length(var.subnet_names) name = var.subnet_names[count.index] virtual_network_name = azurerm_virtual_network.vnet.name resource_group_name = azurerm_virtual_network.vnet.resource_group_name address_prefix = var.subnet_prefixes[count.index] service_endpoints = var.subnet_service_endpoints[count.index] }
3.aksクラスターを作成し、fluxをinstallする ※利用するfileはbedrockの以下path
/bedrock/cluster/azure/aks-gitops
参考までにmain.tf variables.tfはfluxの設定値やkey情報以外はほぼデフォルト値
module "aks" { source = "../../azure/aks" resource_group_name = var.resource_group_name cluster_name = var.cluster_name agent_vm_count = var.agent_vm_count agent_vm_size = var.agent_vm_size dns_prefix = var.dns_prefix vnet_subnet_id = "/subscriptions/${var.azure_subscription_id}/resourceGroups/${var.resource_group_name}/providers/Microsoft.Network/virtualNetworks/${var.virtual_network_name}/subnets/${var.agent_subnet_name}" ssh_public_key = var.ssh_public_key service_principal_id = var.service_principal_id service_principal_secret = var.service_principal_secret service_cidr = var.service_cidr dns_ip = var.dns_ip docker_cidr = var.docker_cidr kubeconfig_filename = var.kubeconfig_filename network_policy = var.network_policy network_plugin = var.network_plugin oms_agent_enabled = var.oms_agent_enabled } module "flux" { source = "../../common/flux" gitops_ssh_url = var.gitops_ssh_url #Azure DevOpsのgitで管理しているリポジトリ gitops_ssh_key = var.gitops_ssh_key #fluxがAzure DevOpsにアクセスするためのkey gitops_path = var.gitops_path #fluxが同期したいマニフェストのpath gitops_poll_interval = var.gitops_poll_interval gitops_label = var.gitops_label gitops_url_branch = var.gitops_url_branch #fluxが同期したいbranch enable_flux = var.enable_flux flux_recreate = var.flux_recreate kubeconfig_complete = module.aks.kubeconfig_done kubeconfig_filename = var.kubeconfig_filename flux_clone_dir = "${var.cluster_name}-flux" acr_enabled = var.acr_enabled gc_enabled = var.gc_enabled } module "kubediff" { source = "../../common/kubediff" kubeconfig_complete = module.aks.kubeconfig_done gitops_ssh_url = var.gitops_ssh_url }
Azure DevOpsでssh_keyを発行する手順はこちらを参照
Connect to your Git repos with SSH - Azure Repos | Microsoft Docs
注意点
fluxで同期を行うmanifestをkustomizeを使って管理している場合はfluxのoptionに注意する必要があります。 bedrockでfluxをインストールするのはhelmを利用しているのですが、「manifestGeneration」optionが変数として受け渡す設計がなされていないので自分たちで修正する必要があります。
/cluster/common/flux/deploy_flux.sh
#!/bin/sh while getopts :b:f:g:k:d:e:c:l:s:r:t:z: option do case "${option}" in b) GITOPS_URL_BRANCH=${OPTARG};; f) FLUX_REPO_URL=${OPTARG};; g) GITOPS_SSH_URL=${OPTARG};; k) GITOPS_SSH_KEY=${OPTARG};; d) REPO_ROOT_DIR=${OPTARG};; e) GITOPS_PATH=${OPTARG};; c) GITOPS_POLL_INTERVAL=${OPTARG};; l) GITOPS_LABEL=${OPTARG};; s) ACR_ENABLED=${OPTARG};; r) FLUX_IMAGE_REPOSITORY=${OPTARG};; t) FLUX_IMAGE_TAG=${OPTARG};; z) GC_ENABLED=${OPTARG};; *) echo "Please refer to usage guide on GitHub" >&2 exit 1 ;; esac done KUBE_SECRET_NAME="flux-ssh" RELEASE_NAME="flux" KUBE_NAMESPACE="flux" CLONE_DIR="flux" REPO_DIR="$REPO_ROOT_DIR/$CLONE_DIR" FLUX_CHART_DIR="chart/flux" FLUX_MANIFESTS="manifests" echo "flux repo root directory: $REPO_ROOT_DIR" rm -rf "$REPO_ROOT_DIR" echo "creating $REPO_ROOT_DIR directory" if ! mkdir "$REPO_ROOT_DIR"; then echo "ERROR: failed to create directory $REPO_ROOT_DIR" exit 1 fi cd "$REPO_ROOT_DIR" || exit 1 echo "cloning $FLUX_REPO_URL" if ! git clone -b "$FLUX_IMAGE_TAG" "$FLUX_REPO_URL"; then echo "ERROR: failed to clone $FLUX_REPO_URL" exit 1 fi cd "$CLONE_DIR/$FLUX_CHART_DIR" || exit 1 echo "creating $FLUX_MANIFESTS directory" if ! mkdir "$FLUX_MANIFESTS"; then echo "ERROR: failed to create directory $FLUX_MANIFESTS" exit 1 fi # call helm template with # release name: flux # git url: where flux monitors for manifests # git ssh secret: kubernetes secret object for flux to read/write access to manifests repo echo "generating flux manifests with helm template" # ここにmanifestGenerationがないため、kustomizeでmanifestをgenerateする場合はoptionを追加する必要があります。 # こんな感じ「--set manifestGeneration=true」 if ! helm template . --name "$RELEASE_NAME" --namespace "$KUBE_NAMESPACE" --values values.yaml --set image.repository="$FLUX_IMAGE_REPOSITORY" --set image.tag="$FLUX_IMAGE_TAG" --output-dir "./$FLUX_MANIFESTS" --set git.url="$GITOPS_SSH_URL" --set git.branch="$GITOPS_URL_BRANCH" --set git.secretName="$KUBE_SECRET_NAME" --set git.path="$GITOPS_PATH" --set git.pollInterval="$GITOPS_POLL_INTERVAL" --set git.label="$GITOPS_LABEL" --set registry.acr.enabled="$ACR_ENABLED" --set syncGarbageCollection.enabled="$GC_ENABLED"; then echo "ERROR: failed to helm template" exit 1 fi # back to the root dir cd ../../../../ || exit 1 echo "creating kubernetes namespace $KUBE_NAMESPACE if needed" if ! kubectl describe namespace $KUBE_NAMESPACE > /dev/null 2>&1; then if ! kubectl create namespace $KUBE_NAMESPACE; then echo "ERROR: failed to create kubernetes namespace $KUBE_NAMESPACE" exit 1 fi fi echo "creating kubernetes secret $KUBE_SECRET_NAME from key file path $GITOPS_SSH_KEY" if kubectl get secret $KUBE_SECRET_NAME -n $KUBE_NAMESPACE > /dev/null 2>&1; then # kubectl doesn't provide a native way to patch a secret using --from-file. # The update path requires loading the secret, base64 encoding it, and then # making a call to the 'kubectl patch secret' command. if [ ! -f "$GITOPS_SSH_KEY" ]; then echo "ERROR: unable to load GITOPS_SSH_KEY: $GITOPS_SSH_KEY" exit 1 fi secret=$(< "$GITOPS_SSH_KEY" base64 -w 0) if ! kubectl patch secret $KUBE_SECRET_NAME -n $KUBE_NAMESPACE -p="{\"data\":{\"identity\": \"$secret\"}}"; then echo "ERROR: failed to patch existing flux secret: $KUBE_SECRET_NAME " exit 1 fi else if ! kubectl create secret generic $KUBE_SECRET_NAME --from-file=identity="$GITOPS_SSH_KEY" -n $KUBE_NAMESPACE; then echo "ERROR: failed to create secret: $KUBE_SECRET_NAME" exit 1 fi fi echo "Applying flux deployment" if ! kubectl apply -f "$REPO_DIR/$FLUX_CHART_DIR/$FLUX_MANIFESTS/flux/templates" -n $KUBE_NAMESPACE; then echo "ERROR: failed to apply flux deployment" exit 1 fi
aksのデプロイが完了するとfluxの同期によりapplicationがデプロイされます。
今回はaksチュートリアルにあるvoteアプリを使いました。
クイック スタート:Azure Kubernetes Service クラスターをデプロイする | Microsoft Docs
デプロイされたらexternal-ipを取得してアクセス出来ます。
$ kubectl get svc -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR azure-vote-back ClusterIP 10.0.111.235 <none> 6379/TCP 10m app=azure-vote-back azure-vote-front LoadBalancer 10.0.55.117 23.102.68.224 80:31559/TCP 10m app=azure-vote-front kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 22m <none>
アクセスすると無事表示されました。
デフォルトですと「default」namespaceにfluxのpodがdeployされるので別で管理したい場合はoptionを変更したほうが良いかもしれません。
$ kubectl get po --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE default azure-vote-back-bc5b86f5f-dpskl 1/1 Running 0 14m default azure-vote-front-ccfd8667f-rt8rc 1/1 Running 0 14m flux flux-6455b87976-7thxx 1/1 Running 0 15m flux flux-memcached-5ff94b674c-t9qcm 1/1 Running 0 15m kube-system azure-cni-networkmonitor-bjxvq 1/1 Running 0 22m kube-system azure-ip-masq-agent-pltrz 1/1 Running 0 22m kube-system azure-npm-79bxx 2/2 Running 0 17m kube-system coredns-544d979687-dzrxw 1/1 Running 0 26m kube-system coredns-autoscaler-546d886ffc-s8ssf 1/1 Running 0 26m kube-system dashboard-metrics-scraper-867cf6588-9llkt 1/1 Running 0 26m kube-system kube-proxy-nvxvm 1/1 Running 0 22m kube-system kubernetes-dashboard-7f7676f7b5-dnrcc 1/1 Running 0 26m kube-system metrics-server-75b8b88d6b-spllf 1/1 Running 1 26m kube-system tunnelfront-6dcfb8d9d9-vrtvr 1/1 Running 0 26m
気になったこと
FluxのDocker Image
検証していて気になったこととして、FluxのDocker imageのリポジトリです。 もともとfluxはFlux projectの「fluxcd」リポジトリからpullしていると想定していましたが 実際はweaveworksのリポジトリからでした。
両リポジトリを比較するとfluxcdの方がversionは新しいようです。
weaveworks weaveworks/flux:1.14.2 fluxcd fluxcd/flux:1.17.1
この違いに関しては
weaveworksの「Flagger」というプロダクトがFluxを利用したcanary デプロイを実現する仕組みがあり対応しているFluxをweaveworks自身が管理しているのではないかと推測しています。
所感
今回はbedrockを使ってAzure上にGitOps対応のAKSクラスターを手動で作成しました。
これらの工程はAzure DevOpsのpipelinesのようなCIツールで自動化することが可能なので自動化できればエンジニアは 特に意識せずにテスト環境を構築できるようになります。
fluxを使ってclusterのgitOpsは実現できていますが、terraformのコードと環境が同期を取っているわけではないので、 実際に環境に関してもgitOpsを行いたい場合はFabrikateを導入してCI pipelineを構築することが必要になります。
検証してみた感想としてあまり抽象化しすぎると、管理や保守の面で結果的に属人化してしまう恐れもあるので テスト環境のような使い捨ての環境を作る場合は、今回のようなterraformスクリプトを自動化するpipelineのみでも十分だと感じました。