GitLab CI/CDパイプライン から Terraform で Azure の ResourceGroup を作成してみました


## 概要

インフラも IaC（コード）化して CIやりましょ！　ってことで、GitLab CI/CDパイプライン を利用して Azure 上に ResourceGroup を作成してみました。

### 実行環境

macOS Monterey 12.3.1 
python 3.8.12
Azure CLI 2.34.1

### 事前準備

- [この記事](https://qiita.com/turupon/items/5d024d43a60cd101be94) にあるように Terraform用のサービスプリンシパルが準備されており、以下のシークレット情報を入手できていること
  - ARM_TENANT_ID
  - ARM_SUBSCRIPTION_ID
  - ARM_CLIENT_ID
  - ARM_CLIENT_SECRET

---

## Gitlab での仕込み

### Gitlab の Personal Access Token の作成

- Gitlab CI/CD の job を実行する権限を有するアクセストークンを発行します（git clone/push時に必要）
   1. サインインアカウントの「Edit profile」-「Access token」を選択
   2. 「Token name」に一意の名称を入力後、「Select scopes」で、「api」を選択する
   3. 「Create personal access token」ボタンを押す
   4. 画面上部に表示される「パーソナルアクセストークン」をどこかにコピーしておく

### Gitlab リポジトリの作成

- GitlabにてTerraformのコードを管理するリポジトリ（プライベートリポジトリで可）を作成し、ローカルで git clone します

### Gitlab へのシークレット登録

- 上記で作成したリポジトリの「設定」-「CI/CD」-「変数」にて、事前準備で入手している情報を使って下記シークレット情報を登録します（「変数の追加」ボタンを押して順次登録）
![スクリーンショット 2022-09-14 21.59.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/51f6504c-8e80-e94c-8ee2-3b2e9be27272.png)

---

## 必要なコード

### Terraformコード

今回は以下の３つのコードで構成されています（リソースグループを作成するだけなので、、、）

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

  # tfstateファイルを gitlab に保存
  backend "http" {
  }
}

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-09-16"
    CostCenter  = "PSG2"
    Environment = "GitLab"
  }
}

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

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

### Gitlab CI/CD の yaml ファイルの作成

リポジトリのルートに .gitlab-ci.yml というファイルを作成します。ここに、Gitlab CI/CDパイプライン のコードを記載していきます。記載方法は [GitLab CI/CD パイプライン設定リファレンス](https://gitlab-docs.creationline.com/ee/ci/yaml/) を参照ください。

```.gitlab-ci.yml
## https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Terraform/Base.gitlab-ci.yml
include:
  - template: Terraform/Base.gitlab-ci.yml

variables:
  TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/tfstate_filename

cache:
  key: tfstate_filename
  paths:
    - .terraform

before_script:
  - export TF_VAR_ARM_TENANT_ID=${ARM_TENANT_ID}
  - export TF_VAR_ARM_SUBSCRIPTION_ID=${ARM_SUBSCRIPTION_ID}
  - export TF_VAR_ARM_CLIENT_ID=${ARM_CLIENT_ID}
  - export TF_VAR_ARM_CLIENT_SECRET=${ARM_CLIENT_SECRET}
  - printenv

stages:
  - prepare
  - validate
  - build
  - deploy
  - cleanup

init:
  stage: prepare
  script:
    - gitlab-terraform init
  rules:
    - if: '$CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH'
      when: always
    - if: '$CI_COMMIT_BRANCH == "main"'

validate:
  stage: validate
  script:
    - gitlab-terraform validate
  rules:
    - if: '$CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH'
      when: always
    - if: '$CI_COMMIT_BRANCH == "main"'

plan:
  stage: build
  script:
    - gitlab-terraform plan
    - gitlab-terraform plan-json
  artifacts:
    name: plan
    paths:
      - plan.cache
    reports:
      terraform: plan.json
  rules:
    - if: '$CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH'
      when: always
    - if: '$CI_COMMIT_BRANCH == "main"'

apply:
  stage: deploy
  script:
    - gitlab-terraform apply
  dependencies:
    - plan
  rules:
    - if: '$CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH'
      when: never
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual

destroy:
  stage: cleanup
  script: 
    - gitlab-terraform destroy
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
```

### .gitlab-ci.ymlの解説

#### include

githubで用意されているテンプレートを利用し、terraform 実行環境のコンテナを使用します。

#### variables

tfstateファイルをおく httpエンドポイント が作成され Jobの実行時にその場所を変数として持たせています。

#### cache

terraform 実行結果の成果物（tfstateファイル）を stage 間で利用できるようにするための設定です。

#### before_script

このリポジトリに設定された変数を読み込み、環境変数として定義します。

#### stages

パイプラインにおける各 stage を設定します。
Terraform なので、init > validate > plan > apply > destory という構成になっています。

#### init

terraform init するステージです。
mainブランチに対するマージリクエストが承認（mainブランチが更新）されると自動実行します。

#### validate

terraform validate するステージです。
mainブランチに対するマージリクエストが承認（mainブランチが更新）されると自動実行します。

#### plan

terraform plan するステージです。
mainブランチに対するマージリクエストが承認（mainブランチが更新）されると自動実行します。
terraform plan の実行結果は jsonファイルとして artifacts に保存されます。

#### apply

terraform apply するステージです。
手動による実行となります。

#### destory

terraform destory するステージです。
手動による実行となります。

---

## git push してみる

これで、準備完了です。git push することにより、terraform plan による Azure への ResourceGroup の作成がおこなわれます。

### push

```
git add .
git commit -m "Actions commit"
git push -u origin main
```

### Actions

GitHub Actions のジョブを確認します。順次処理が実施され、plan までエラーもなく正常に終了しています。
※下図はすべてのジョブを実行したあとのものです、、、、
![スクリーンショット 2022-09-15 1.26.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/dddf195c-6eda-50f8-9c20-03d192873631.png)

---

## Azure リソースの作成と削除

CI/CDパイプラインのジョブ からリソースの作成（ステージ：deploy）と削除（ステージ：cleanup）を手動実行します。

### リソースの作成

CI/CDパイプラインのジョブ において、terraform apply（ステージ：deploy）を実行すると ResourceGroup の作成がおこなわれます。 問題なくリソースが作成されていることを確認できます。

```
$ az group show --name rg_ituru_gitlab
{
  "id": "/subscriptions/xxxxxxxx-1717-4343-9797-zzzzzzzzzzzz/resourceGroups/rg_ituru_gitlab",
  "location": "japaneast",
  "managedBy": null,
  "name": "rg_ituru_gitlab",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": {
    "CostCenter": "PSG2",
    "Environment": "GitLab",
    "owner": "ituru",
    "period": "2022-09-16"
  },
  "type": "Microsoft.Resources/resourceGroups"
}
```

tfstateファイルは以下の場所にあります。ダウンロードして確認ください。
![スクリーンショット 2022-09-15 1.54.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544022/c606d1dd-8630-3792-3800-0888d0434f77.png)


### リソースの削除

CI/CDパイプラインのジョブ において、terraform destory（ステージ：cleanup）を実行すると ResourceGroup の削除がおこなわれます。 問題なくリソースが削除されていることを確認できます。

```
$ az group show --name rg_ituru_gitlab
(ResourceGroupNotFound) Resource group 'rg_ituru_gitlab' could not be found.
Code: ResourceGroupNotFound
Message: Resource group 'rg_ituru_gitlab' could not be found.
```

---

### まとめ

GitLab CI/CDパイプライン を利用した Terraform による Azureリソースの作成・削除するための実装方法を簡単に理解することができました。
ちなみに、GitHub Actions でも同様なことを試しています。

- [GitHub Actions から Terraform で Azure の ResourceGroup を作成してみました](https://qiita.com/turupon/items/0068fe23930116e329a5)
- [GitHub Actions から Terraform で Azure の ResourceGroup を更新（変更）してみました](https://qiita.com/turupon/items/7096e4df85ce7e95bd29)
- [GitHub Actions から Terraform destroy してみました](https://qiita.com/turupon/items/695063c9103348a079da)

### 参考記事

以下の記事を参考にさせていただきました。感謝申し上げます
[Terraform と GitLab CI で インフラCI/CD入門](https://techblog.istyle.co.jp/archives/6265)
[Terraform プロジェクトの .gitlab-ci.yml テンプレート](https://labs.septeni.co.jp/entry/2018/12/17/120000)
[terraform の tfstate の管理に GitLab を使ってみた](https://dev.classmethod.jp/articles/2022-07-11-tfstate-backend-using-gitlab/)
[GitとCI/CDに関する知識ゼロのSEが、GitLabでhttpsでクローンとプッシュを試すだけの記事](https://blogs.networld.co.jp/entry/2022/02/21/090121)
[GitとCI/CDに関する知識ゼロのSEが、リポジトリにソースコードがない状態でGitLabのCI/CDパイプライン (DAG) を構成する話](https://blogs.networld.co.jp/entry/2022/04/01/090000)
[Predefined variables reference](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)