AKS拡張機能のFluxでイメージ自動更新まで設定する

# はじめに

AzureのK8sサービスであるAKSでは拡張機能としてCDツールの[Flux](https://fluxcd.io/)が導入できます。さらにFluxはイメージ自動更新までできますが、Azure公式のドキュメントではそこまでの手順を解説してくれていませんでした。そのため、Flux導入からイメージ自動更新まで設定する流れをまとめました。

# 環境

## クラウド

リソースグループにAKSおよびACRが作成されている状態から開始します。GitHubのリポジトリも予め用意しておきます。

AKS拡張機能でインストールするとns:flux-systemにfluxconfig-controller等のFluxコントローラ一式がデプロイされます。ns:flux-testにFluxで管理する対象リソースをデプロイします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1356950/d0bd20cf-c7d3-0a21-5cd3-a41e6fd6ec22.png)

## ディレクトリ構造

GitHubのリポジトリは以下のディレクトリ構造です。

`flux`ディレクトリ配下はAKS拡張機能のFluxをインストールするTerraformコード群です。

`flux-test`はテスト用のNamespaceです。`test-app`配下にテスト用アプリケーションのファイル群を配置します。`k8s`はテスト用アプリケーションのk8sマニフェスト群です。`terraform`はfluxconfigをAzureで設定するterraformファイルです。

```
.
├── flux
│   ├── base.tf
│   └── container.tf
└── flux-test
    ├── ns.yaml
    └── test-app
        ├── k8s
        │   ├── flux-configs.yaml
        │   ├── kustomization.yaml
        │   └── pod.yaml
        └── terraform
            └── container.tf
```

# 手順

## Fluxのインストール

AKS拡張機能でFluxをAKSにインストールします。

### リソースプロバイダ

Fluxをインストールする前に以下のリソースプロバイダーを有効にします。[チュートリアル: GitOps with Flux v2 を使ってアプリケーションをデプロイする](https://learn.microsoft.com/ja-jp/azure/azure-arc/kubernetes/tutorial-use-gitops-flux2?tabs=azure-cli#common-to-both-cluster-types)

- Microsoft.ContainerService(既定で有効)
- Microsoft.Kubernetes
- Microsoft.KubernetesConfiguration

`Microsoft.ContainerService`は既定で有効なためそれ以外の2つを有効化します。

``` flux/base.tf
resource "azurerm_resource_provider_registration" "k8s" {
  name = "Microsoft.Kubernetes"
}

resource "azurerm_resource_provider_registration" "k8sconfiguration" {
  name = "Microsoft.KubernetesConfiguration"
}
```

### Fluxのインストール

AKS拡張機能のFluxをインストールします。イメージ自動更新をする場合は明示的にオプションを指定します。[Flux クラスター拡張機能を使用してデプロイするコントローラーを制御する](https://learn.microsoft.com/ja-jp/azure/azure-arc/kubernetes/tutorial-use-gitops-flux2?tabs=azure-cli#control-which-controllers-are-deployed-with-the-flux-cluster-extension)

``` flux/container.tf
resource "azurerm_kubernetes_cluster_extension" "flux" {
  name           = "Flux"
  cluster_id     = AKS_CLUSTER_ID
  extension_type = "microsoft.flux"

  configuration_settings = {
    "image-automation-controller.enabled" = true # 自動イメージ更新機能を有効化
    "image-reflector-controller.enabled"  = true # 自動イメージ更新機能を有効化
  }

  depends_on = [azurerm_resource_provider_registration.k8s, azurerm_resource_provider_registration.k8sconfiguration]
}
```

## GitOpsの設定

FluxのGitOps設定をします。

### Namespace作成

Fluxでデプロイするリソース群を配置するNamespaceを作成します。

``` flux-test/ns.tf
apiVersion: v1
kind: Namespace
metadata:
  name: flux-test
```

### GitHub認証情報の作成

GitOps設定をする前にGitHubへアクセスするための認証情報を作成します。GitHubにアクセスする認証情報(ユーザー名、パーソナルアクセストークン)を使って以下のようにSecretを作成します。[ソースでの認証のためのローカル シークレット](https://learn.microsoft.com/ja-jp/azure/azure-arc/kubernetes/gitops-flux2-parameters#local-secret-for-authentication-with-source)

``` sh
kubectl create secret generic -n flux-test github-auth --from-literal=username=<GitHubユーザー名> --from-literal=password=<GitHubパーソナルアクセストークン>
```

> 認証情報をファイルに残したくなかったためコマンドでSecretを作成しています。

### fluxconfigの設定

AKS拡張機能でFluxをインストールするとfluxconfigをAzureで設定できます。`git_repository.local_auth_reference`は先ほど作成したGitHub認証情報のSecretを指定します。`kustomizations.path`はkustomization.yamlを配置しているディレクトリのパスを指定します。このfluxconfigのリソース名はAKSクラスタ内で一意でないといけません。

``` flux-test/test-app/terraform/container.tf
resource "azurerm_kubernetes_flux_configuration" "test_app" {
  name       = "flux-test-test-app"
  cluster_id = AKS_CLUSTER_ID
  namespace  = "flux-test"

  git_repository {
    url                      = "https://github.com/ORG/REPONAME"
    reference_type           = "branch"
    reference_value          = "main"
    sync_interval_in_seconds = 60
    timeout_in_seconds       = 60
    local_auth_reference     = "github-auth" # GitHub認証のSecret名
  }

  kustomizations {
    depends_on                 = []
    garbage_collection_enabled = false
    name                       = "kustomization"
    path                       = "flux-test/test-app/k8s"
    recreating_enabled         = false
    retry_interval_in_seconds  = 60
    sync_interval_in_seconds   = 60
    timeout_in_seconds         = 60
  }
}
```

### 動作確認

fluxconfigをデプロイするとGitHubに格納している以下マニフェストをFluxが自動でデプロイしてくれているはずです。

``` flux-test/test-app/k8s/pod.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    flux: v1
  name: test-app
spec:
  containers:
  - image: REGISTRYNAME.azurecr.io/test-app:20240814-1210
    name: test-app
```

``` flux-test/test-app/k8s/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: flux-test
metadata:
  name: test-app
resources:
- pod.yaml
```

以下コマンドで現在のPodラベルを確認します。

``` sh
$ kubectl get pod -n flux-test --show-labels
NAME      READY   STATUS    RESTARTS   AGE     LABELS
test-app  1/1     Running   0          9m56s   flux=v1,kustomize.toolkit.fluxcd.io/name=test-app-kustomization,kustomize.toolkit.fluxcd.io/namespace=flux-test
```

pod.yamlのラベル`flux: v1`を`flux: v2`に変更してGitHubにpushします。しばらくするとPodのラベルが更新されているはずです。

``` sh
$ kubectl get pod -n flux-test  --show-labels
NAME       READY   STATUS    RESTARTS   AGE   LABELS
test-app   1/1     Running   0          13m   flux=v2,kustomize.toolkit.fluxcd.io/name=test-app-kustomization,kustomize.toolkit.fluxcd.io/namespace=flux-test
```

#### Cluster Scopeのリソースがある場合

Namespaceに納まらないリソースが含まれると`kustomize-controller`のPodログにcluster scopeのリソースに関するエラーが出力されデプロイできない。

``` json
{"level":"error","ts":"2024-08-14T09:00:23.222Z","msg":"Reconciliation failed after 111.770573ms, next try in 1m0s","controller":"kustomization","controllerGroup":"kustomize.toolkit.fluxcd.io","controllerKind":"Kustomization","Kustomization":{"name":"flux-test-test-app-kustomization","namespace":"flux-test"},"namespace":"flux-test","name":"flux-test-test-app-kustomization","reconcileID":"99f2ef4a-584c-467f-bebe-247776bd6c07","revision":"main@sha1:9449402cada8205e86d087b48a8cac7c29da85aa","error":"ClusterRoleBinding/testtest dry-run failed (Forbidden): clusterrolebindings.rbac.authorization.k8s.io \"testtest\" is forbidden: User \"system:serviceaccount:flux-test:flux-applier\" cannot patch resource \"clusterrolebindings\" in API group \"rbac.authorization.k8s.io\" at the cluster scope\n"}
```

fluxconfigのScopeをClusterに変えるかNamespace内のsa:flux-applierにcluster-adminを付ければデプロイできるようになる。

``` sh
$ kubectl create clusterrolebinding --serviceaccount="flux-test:flux-applier" --clusterrole="cluster-admin" my-flux-applier-crb
```

## イメージ自動更新の設定

Fluxのイメージ自動更新を設定します。

### ACR認証情報の作成

イメージ自動更新の設定をする前にFluxがACRを参照できるようにします。以下コマンドでFlux専用のサービスプリンシパルを作成します。[サービス プリンシパルの作成](https://learn.microsoft.com/ja-jp/azure/container-registry/container-registry-auth-kubernetes#create-a-service-principal)

``` sh
$ az ad sp create-for-rbac --name acr-pull-for-flux --role acrpull --scopes /subscriptions/SUBSCRIPTIONID/resourceGroups/RESOUCEGROUPNAME/providers/Microsoft.ContainerRegistry/registries/REGISTRYNAME
```

上記コマンドを実行すると以下のような出力が得られます。

``` json
{
  "appId": "your-app-id",
  "displayName": "acr-pull-for-flux",
  "password": "YOURPASSWORD",
  "tenant": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```

上記の出力結果を使ってdocker-registryタイプのSecretを作成します。Secretはこの後作るImageRepositoryと同じNamesapceに作ります。[イメージのプル シークレットを作成する](https://learn.microsoft.com/ja-jp/azure/container-registry/container-registry-auth-kubernetes#create-an-image-pull-secret)

``` sh
$ kubectl create secret docker-registry acr-pull-for-flux \
    --namespace flux-test \
    --docker-server=REGISTRYNAME.azurecr.io \
    --docker-username=your-app-id \
    --docker-password=YOURPASSWORD
```

> 認証情報をファイルに残したくなかったためコマンドでSecretを作成しています。

### イメージ自動更新の設定

`ImageRepository`、`ImagePolicy`、`ImageUpdateAutomation`の3つのリソースを作成します。`ImageRepository`では先ほど作成したdocker-registryタイプのSecret名を指定します。`ImagePolicy`では`YYYYMMDD-hhmm`の形式でタグ付けされるイメージの最新を`alphabetical`の`asc`(昇順)で指定します。`ImageUpdateAutomation`のソースはAzureで作成したfluxconfigの名前を指定します。

``` flux-test/test-app/k8s/flux-configs.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: test-app
  namespace: flux-test
spec:
  image: REGISTRYNAME.azurecr.io/test-app
  interval: 1m
  secretRef:
    name: acr-pull-for-flux # 予め作成したACR pull用のSecret
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: test-app
  namespace: flux-test
spec:
  imageRepositoryRef:
    name: test-app
  policy:
    alphabetical:
      order: asc # 昇順で最後のイメージを選択
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
  name: test-app
  namespace: flux-test
spec:
  interval: 1m
  sourceRef:
    kind: GitRepository
    name: flux-test-test-app # Azureで作成したfluxconfigの名前
  git:
    commit:
      author:
        email: fluxcdbot@users.noreply.github.com
        name: fluxcdbot
    push:
      branch: main
  update:
    strategy: "Setters"
    path: ./
```

### マニフェストのマーカー

マニフェストのイメージ更新したい箇所にコメントでマーカーを設定します。[Configure image update for custom resources](https://fluxcd.io/flux/guides/image-update/#configure-image-update-for-custom-resources)

``` flux-test/test-app/k8s/pod.yaml
...
spec:
  containers:
  - image: REGISTRYNAME.azurecr.io/test-app:20240813-1823 # {"$imagepolicy": "flux-test:test-app"}
    name: test-app
...
```

### 動作確認

現在のPodイメージを確認します。

``` sh
$ kubectl describe pod -n flux-test test-app | grep Image:
    Image:          REGISTRYNAME.azurecr.io/test-app:20240813-1823
```

コンテナイメージを適当に更新しACRに最新のイメージを格納します。しばらくした後にPodイメージを確認すると以下のように更新されているはずです。

``` sh
$ kubectl describe pod -n test test-app | grep Image:
    Image:          REGISTRYNAME.azurecr.io/test-app:20240814-1210
```

Fluxによりリモートリポジトリが更新されているため最後にリポジトリをpullしておく。

# まとめ

拡張機能なので自分たちでツールを管理するよりは楽になるはず。ArgoCDとは違いGUIはありませんが、最低限使う分には支障ないでしょう。