【Terraform de Azure】 Azure Kubernetes Service (AKS) with Azure CNI の環境を構築してみました

### 概要

「Infrastructure as Code」ということで、Terraform を用いて Azure Kubernetes Service (AKS) with Azure CNI の環境を構築してみました。 with CNI環境はAKSの本番環境です。[こちらの記事](https://qiita.com/turupon/items/4220f0845ccd3549d355) は Azure CLIで作成しましたが、今回はTerraformでの作成となります。

### ローカル環境

- macOS Monterey 12.3
- python 3.8.12
- Azure CLI 2.34.1
- terraform v1.0.11

### 前提条件

1. Azure環境がすでに用意されていること（テナント/サブスクリプション）
2. ローカル環境に「azure cli」がインストールされていること
3. ローカル環境に「terraform」環境が構成されていること
4. TerraformでAzure上に環境構築するための[サービスプリンシパルが作成](https://qiita.com/turupon/items/5d024d43a60cd101be94)されており、Terraform のためのローカル環境変数が定義されていること

---

## AKS with Azure CNI 環境の構築

### terraform 定義ファイルの作成

プロバイダの定義

```main.tf
# プロバイダーの定義
terraform {
  required_providers {
    azurerm =  "~> 2.33"
  }
}

provider "azurerm" {
  features {}
  tenant_id       = var.ARM_TENANT_ID
  subscription_id = var.ARM_SUBSCRIPTION_ID
  client_id       = var.ARM_CLIENT_ID
  client_secret   = var.ARM_CLIENT_SECRET
}


# リソースグループ
resource "azurerm_resource_group" "this" {
  name     = var.resource_group_name
  location = var.region
  tags     = var.tags_def
}
```

パラメータ定義ファイル

```variables.tf
# 環境変数（Azureサービスプリンシパル）
variable ARM_TENANT_ID {}
variable ARM_SUBSCRIPTION_ID {}
variable ARM_CLIENT_ID {}
variable ARM_CLIENT_SECRET {}

# タグ情報
variable tags_def {
  default = {
    owner      = "ituru"
    period     = "2022-04-28"
    CostCenter = "PSG2"
    Environment = "CPDemo"
  }
}

# 各種パラメータ
variable region {}                  // 利用リージョン
variable resource_group_name {}     // リソースグループ名

variable vnet_name {}               // vNet名
variable vnet_address_space {}      // vNetアドレス範囲
variable subnet_name {}             // サブネット名
variable subnet_address {}          // サブネットアドレス
variable dns_service_ip {}          // サービス用DNSアドレス
variable docker_address {}          // Dockerブリッジアドレス
variable service_address {}         // サービス用ネットワークアドレス

variable aks_cluster_name {}        // AKSクラスタ名
variable dns_prefix {}              // DNSプレフィックス
variable default_node_pool_name {}  // ノードプール名
variable enable_auto_scaling {}     // 自動拡張可否
variable vm_size {}                 // 仮想マシンサイズ
variable node_count {}              // 起動時ノード数
variable max_count {}               // 最大ノード数
variable min_count {}               // 最小ノード数
```

パラメータ値定義ファイル

```terraform.tfvars
# 環境変数の定義（Azureサービスプリンシパル）
ARM_TENANT_ID       = "zzzzzzzz-cccc-4645-5757-zzzzzzzzzzzz"
ARM_SUBSCRIPTION_ID = "xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz"
ARM_CLIENT_ID       = "xxxxxxxx-xxxx-4444-9922-xxxxxxxxxxxx"
ARM_CLIENT_SECRET   = "hogehogehogehogehogehogehogehogege"

# パラメータ値の定義
region                  = "japaneast"           // 利用リージョン
resource_group_name     = "rg_ituru_aks01"      // リソースグループ名

vnet_name               = "vnet_ituru_aks01"    // vNet名
vnet_address_space      = "10.0.0.0/16"         // vNetアドレス範囲
subnet_name             = "snet_ituru_aks01"    // サブネット名
subnet_address          = "10.0.1.0/24"         // サブネットアドレス
dns_service_ip          = "10.1.0.10"           // サービス用DNSアドレス
docker_address          = "172.17.0.1/16"       // Dockerブリッジアドレス
service_address         = "10.1.0.0/24"         // サービス用ネットワークアドレス

aks_cluster_name        = "aks_ituru_cp01"      // AKSクラスタ名
dns_prefix              = "cpcluster01"         // DNSプレフィックス
default_node_pool_name  = "cpdemo01"            // ノードプール名
enable_auto_scaling     = "true"                // 自動拡張可否
vm_size                 = "Standard_D2_v2"      // 仮想マシンサイズ
node_count              = "3"                   // 起動時ノード数
max_count               = "5"                   // 最大ノード数
min_count               = "3"                   // 最小ノード数
```

Azure Kubernetes Service (AKS) 定義ファイル

```aks.tf
# AKS
resource "azurerm_kubernetes_cluster" "this" {
  name                = var.aks_cluster_name
  location            = azurerm_resource_group.this.location
  resource_group_name = azurerm_resource_group.this.name
  dns_prefix          = var.dns_prefix
  tags                = var.tags_def

  default_node_pool {
    name                = var.default_node_pool_name
    vm_size             = var.vm_size
    node_count          = var.node_count
    vnet_subnet_id      = azurerm_subnet.internal.id
    type                = "VirtualMachineScaleSets"
    tags                = var.tags_def

    enable_auto_scaling = var.enable_auto_scaling
    max_count           = var.max_count
    min_count           = var.min_count
  }

  service_principal {
    client_id     = var.ARM_CLIENT_ID
    client_secret = var.ARM_CLIENT_SECRET
  }

  network_profile {
    network_plugin     = "azure"
    network_policy     = "calico"
    dns_service_ip     = var.dns_service_ip
    docker_bridge_cidr = var.docker_address
    service_cidr       = var.service_address
    load_balancer_sku  = "Standard"
  }

  lifecycle {
    prevent_destroy = true
  }
}
```

Azure CNI 定義ファイル

```network.tf
// 仮想ネットワーク
resource "azurerm_virtual_network" "this" {
  name                = var.vnet_name
  address_space       = [var.vnet_address_space]
  location            = azurerm_resource_group.this.location
  resource_group_name = azurerm_resource_group.this.name
  tags                = var.tags_def
}

// サブネット
resource "azurerm_subnet" "internal" {
  name                  = var.subnet_name
  resource_group_name   = azurerm_resource_group.this.name
  virtual_network_name  = azurerm_virtual_network.this.name
  address_prefixes      = [var.subnet_address]
}
```

Output 定義ファイル

```output.tf
# AKSクラスタのクライアント証明書を出力
output "client_certificate" {
  value = azurerm_kubernetes_cluster.this.kube_config.0.client_certificate
}

# AKSクラスタのkubeconfigを出力
output "kube_config" {
  value = azurerm_kubernetes_cluster.this.kube_config_raw
  sensitive = true
}

# AKSクラスタのFQDNを出力
output "fqdn" {
  value = azurerm_kubernetes_cluster.this.fqdn
}

# AKSクラスタのIDを出力
output "cluster_id" {
  value = azurerm_kubernetes_cluster.this.id
}
```

### terraform の実行

```
## init
$ terraform init
    :
Terraform has been successfully initialized!

## plan
$ terraform plan
    :
Plan: 4 to add, 0 to change, 0 to destroy.

## apply
$ terraform apply
    :
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
```

Output 出力結果

```
Outputs:

client_certificate = "LS0tLS1..........................FURS0tLS0tCg=="
cluster_id = "/subscriptions/xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz/resourceGroups/rg_ituru_aks01/providers/Microsoft.ContainerService/managedClusters/aks_ituru_cp01"
fqdn = "cpcluster01-adf56c0d.hcp.japaneast.azmk8s.io"
kube_config = <sensitive>
```

### ローカルの作業ディレクトの状況

```
$ tree -a
.
├── .terraform
│   └── providers
│       └── registry.terraform.io
│           └── hashicorp
│               └── azurerm
│                   └── 2.99.0
│                       └── darwin_amd64
│                           └── terraform-provider-azurerm_v2.99.0_x5
├── .terraform.lock.hcl
├── aks.tf
├── main.tf
├── network.tf
├── outputs.tf
├── terraform.tfstate
├── terraform.tfstate.backup
├── terraform.tfvars
└── variables.tf
```

---

## terraform 実行後の確認

### Azure CLI からの確認

```
## 作成先サブスクリプションへの接続
$ az account set --subscription '<Subscription_Name>'


## AKSクラスタの表示
$ az aks list -g rg_ituru_aks01 -o table
Name            Location    ResourceGroup    KubernetesVersion    ProvisioningState    Fqdn
--------------  ----------  ---------------  -------------------  -------------------  --------------------------------------------
aks_ituru_cp01  japaneast   rg_ituru_aks01   1.21.9               Succeeded            cpcluster01-adf56c0d.hcp.japaneast.azmk8s.io


## AKS node pool の表示
$ az aks nodepool list -g rg_ituru_aks01 --cluster-name aks_ituru_cp01 -o table
Name      OsType    KubernetesVersion    VmSize          Count    MaxPods    ProvisioningState    Mode
--------  --------  -------------------  --------------  -------  ---------  -------------------  ------
cpdemo01  Linux     1.21.9               Standard_D2_v2  3        30         Succeeded            System
```

### AKSクラスタの操作

```
## クラスタの起動
$ az aks start -g rg_ituru_aks01 -n aks_ituru_cp01

## クラスタの停止
$ az aks stop -g rg_ituru_aks01 -n aks_ituru_cp01
```


### ローカル環境からクラスタ接続のための認証情報の取得

```
$ az aks get-credentials --resource-group rg_ituru_aks01 --name aks_ituru_cp01
```

~/.kube/configファイルに接続情報が書き込まれることで、ローカル環境からクラスタ操作できるようになります。

### AKSクラスタの確認

テスト用のPod起動

```
$ kubectl run --image=centos:centos7 --restart=Never --rm  -it test -- bash
If you don't see a command prompt, try pressing enter.
[root@test /]# 
[root@test /]# curl ifconfig.me
20.89.26.110
```

別コンソールを立ち上げて、Podの確認

```
## ノードの確認
$ kubectl get nodes -o wide
NAME                               STATUS   ROLES   AGE     VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
aks-cpdemo01-17154442-vmss000003   Ready    agent   3m41s   v1.21.9   10.0.1.4      <none>        Ubuntu 18.04.6 LTS   5.4.0-1073-azure   containerd://1.4.12+azure-3
aks-cpdemo01-17154442-vmss000004   Ready    agent   3m30s   v1.21.9   10.0.1.35     <none>        Ubuntu 18.04.6 LTS   5.4.0-1073-azure   containerd://1.4.12+azure-3
aks-cpdemo01-17154442-vmss000005   Ready    agent   3m41s   v1.21.9   10.0.1.66     <none>        Ubuntu 18.04.6 LTS   5.4.0-1073-azure   containerd://1.4.12+azure-3


## Podの確認
$ kubectl get pod -o wide           
NAME   READY   STATUS    RESTARTS   AGE   IP          NODE                               NOMINATED NODE   READINESS GATES
test   1/1     Running   0          76s   10.0.1.39   aks-cpdemo01-17154442-vmss000004   <none>           <none>
```

### 作成したリソースの削除

```
## destroy
$ terraform destroy --auto-approve
```

---

### まとめ

Terraform でサクッと Azure Kubernetes Service (AKS) with Azure CNI構成を作成できました。 Azure Portal や Azure CLI で構築するのもいいですが、IaC化もいいですね。