はじめに

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 キーを生成します
  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 の利用もぜひ検討してみてください。