Terraformを使ってAKS上にFluxを導入する手順とデモ
2023-07-06
azblob://2023/07/05/eyecatch/2023-07-06-deploy-flux-on-aks-with-terraform-000.jpg

はじめに

GitOpsは、開発者がコードをGitリポジトリにプッシュすることで自動的にインフラストラクチャーが更新される仕組みです。

Fluxは、GitOpsを実現するためのツールであり、KubernetesのmanifestファイルをGitリポジトリに保存し、変更があるたびに自動でデプロイすることができます。

本記事では、Terraformを用いてAKS上にFluxを導入する手順を説明し、GitOpsのデモを行います。

AKS上にFluxを導入する手順

連携先のGitHubリポジトリの準備

GitHubリポジトリが空だとTerraformの実行時にエラーになるため、mainブランチにreadmeか何かを入れておいてください。

続いてGitHubとの認証情報の準備です。
今回はDeploy keyを用いてFluxからGitHubに接続する方針で説明します。

Deploy keyの作成方法は下記です。

  1. 接続に使用するSSHキーを生成します
    1. 参考:https://docs.github.com/ja/authentication/connecting-to-github-with-ssh…
  2. 対象のリポジトリの「Setting」から、「Deploy keys」を開きます
  3. 「Add deploy key」をクリックします
  4. Titleに任意の名称を設定し、SSHキーの公開鍵をコピペし、「Allow write access」のチェックを入れて「Add key」します

SSHキーの秘密鍵はTerraformの実行時に必要になります。

AKSとFluxの導入

AKSの作成とFluxの導入はTerraformで実現しましょう。

主に azurerm_kubernetes_cluster flux_bootstrap_git を利用します。検証時点ではflux_bootstrap_gitのバージョンは `1.0.0-rc.5` でした。

main.tf

terraform {
  required_providers {
    kubernetes = {
      source = "hashicorp/kubernetes"
    }
    flux = {
      source  = "fluxcd/flux"
      version = "1.0.0-rc.5"
    }
  }
}

provider "azurerm" {
  subscription_id = var.subscription_id
  client_id       = var.sp_client_id
  client_secret   = var.sp_client_secret
  tenant_id       = var.sp_tenant_id
  features {}
}

provider "kubernetes" {
  host                   = azurerm_kubernetes_cluster.this.kube_config.0.host
  client_certificate     = base64decode(azurerm_kubernetes_cluster.this.kube_config.0.client_certificate)
  client_key             = base64decode(azurerm_kubernetes_cluster.this.kube_config.0.client_key)
  cluster_ca_certificate = base64decode(azurerm_kubernetes_cluster.this.kube_config.0.cluster_ca_certificate)
}

provider "flux" {
  kubernetes = {
    host                   = azurerm_kubernetes_cluster.this.kube_config.0.host
    username               = azurerm_kubernetes_cluster.this.kube_config.0.username
    password               = azurerm_kubernetes_cluster.this.kube_config.0.password
    client_certificate     = base64decode(azurerm_kubernetes_cluster.this.kube_config.0.client_certificate)
    client_key             = base64decode(azurerm_kubernetes_cluster.this.kube_config.0.client_key)
    cluster_ca_certificate = base64decode(azurerm_kubernetes_cluster.this.kube_config.0.cluster_ca_certificate)
  }
  git = {
    url = "ssh://git@github.com/${var.github_organization_name}/${var.github_repository_name}.git"
    ssh = {
      username    = "git"
      private_key = file(var.gitops_ssh_private_key_file)
    }
  }
}

locals {
  resource_group_name = "rg-gitops"
  aks_name            = "aks-gitops"
  namespace_flux      = "flux-system"
}

resource "azurerm_resource_group" "this" {
  name     = local.resource_group_name
  location = "japaneast"
}

resource "azurerm_kubernetes_cluster" "this" {
  name                      = local.aks_name
  location                  = "japaneast"
  resource_group_name       = azurerm_resource_group.this.name
  automatic_channel_upgrade = "patch"
  dns_prefix                = "dns"

  default_node_pool {
    name       = "system"
    node_count = 1
    vm_size    = "Standard_B2s"
    type       = "VirtualMachineScaleSets"
  }

  identity {
    type = "SystemAssigned"
  }

  network_profile {
    network_plugin = "azure"
  }
}

resource "kubernetes_namespace" "flux" {
  metadata {
    name = local.namespace_flux
  }
  depends_on = [
    azurerm_kubernetes_cluster.this
  ]

  lifecycle {
    ignore_changes = [
      metadata
    ]
  }
}

resource "flux_bootstrap_git" "this" {
  namespace = kubernetes_namespace.flux.metadata[0].name
  path      = "clusters/${local.aks_name}"
}

variables.tf

variable "subscription_id" {
  description = "AKS作成先のサブスクリプションのID"
  type        = string
}

variable "sp_tenant_id" {
  description = "AzureのテナントのID"
  type        = string
}

variable "sp_client_id" {
  description = "Azureに接続するサービスプリンシパルのID"
  type        = string
}

variable "sp_client_secret" {
  description = "Azureに接続するサービスプリンシパルのシークレット"
  sensitive   = true
  type        = string
}

variable "github_organization_name" {
  description = "GitHubリポジトリが所属する組織名またはユーザー名"
  type        = string
}

variable "github_repository_name" {
  description = "GitHubリポジトリ名"
  type        = string
}

variable "gitops_ssh_private_key_file" {
  description = "GitHubリポジトリのDeploy keyの秘密鍵が記載されたファイルパス"
  type        = string
}

terraform apply時は、variables.tfのdescriptionの説明を参考にパラメータを渡してあげてください。

providerの記述が特徴的かと思います。
namespaceの作成やFluxの導入時にkubernetesへの接続情報が必要になるのですが、 `provider "kubernetes"` や `
provider "flux"` の記載でazurerm_kubernetes_clusterのアウトプットから拾うようにしています。

他は各リソースのterraformドキュメントに記載されている内容とあまり差が無いと思います。

GitOpsのデモ

terraform applyが成功すると、GitOps用のGitHubリポジトリ内に `clusters/aks-gitops/flux-system` フォルダが作成され、ここにFluxの管理ファイルが保管されています。

mainブランチの `clusters/aks-gitops` フォルダ内がFluxにより監視されているので、ここに好きなファイル名.yamlでマニフェストファイルを追加してあげるとAKSにデプロイされます。

GitHubを監視する周期はデフォルト設定だと1分です。

試しに下記のマニフェストファイルを `clusters/aks-gitops/sample-namespace.yaml` としてmainブランチにコミット、プッシュしてみましょう。

sample-namespace.yaml

kind: Namespace
apiVersion: v1
metadata:
  name: sample
  labels:
    name: sample

kubectl applyすると `sample` という名前のnamespaceが作成されるマニフェストファイルですね。

namespaceが作成されたかどうかは好きな方法で確認してOKですが、Azureポータルから見るのが気軽ですかね?

AzureポータルからTerraformで作成されたAKSの「Kubenetes リソース」→「名前空間」を選択してください。

kubectl applyせずに、マニフェストファイルをプッシュしてから1分以内にnamespaceが作成されたと思います!

ファイルの削除をコミットすればnamespaceが削除されます、素晴らしいー

まとめ

Terraformを使用することで、簡単にFluxを用いたGitOps環境が構築できることが分かりました。

GitOpsの実現手段としてはArgoCDもありますが、Fluxの利用もぜひ検討してみてください。