terraformでGCPのID払い出しを簡略化したい

# やりたいこと・構想
- 多人数にGCPを好き勝手検証できるような環境を提供したく、その際に必要なIDの払い出しのプロセスを自動化したい
- IaCに興味があったので、terraformを利用してGCPの組織を管理してみて、学びを得たい。

## 機能
- 管理者側および利用者側に馴染みのあるインターフェース（Microsoft365のサービス)を提供する（今回は未実装）
- Github actionsを利用してインフラ環境のデプロイを自動化する（今回記事にした内容）

## 概要

### 前提
- サービスアカウントの権限を借用してterraform経由で プロジェクト作成を実施するため、事前にサービスアカウントに組織レベルの権限を付与する必要あり。
- github actionsでサービスアカウントの権限を借用するにあたってアクセスキーを発行しておく必要あり。（OIDC認証を利用したほうがアクセスキーを管理する必要がないが、今回はキーを利用する）
- 企業向けのgithubを利用しているため、github actionsには承認されているものしか利用できない。

![qiita_lac.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3550845/34137c80-ee07-f47e-b9ac-8abcc7a04346.png)

流れ
①ローカルPCでどんなプロジェクトを作成してどのメールアドレスのアカウントをメンバーとするかをファイルに記載
②編集したファイルをgithubへ反映させる。
③そのアクションをトリガーにgithub側の仮想マシンが立ち上がり、githubのワークフローファイルに記載の操作を実施する。
④その一連の操作の中でterraformがインストールされ、ソースコードを参照してgcpにデプロイする。

### 今回私が躓いたところ/注意点
- 組織レベルの権限を付与する為のコンソール操作がわからず苦労した（結局gloud cli経由で実施、設定後に簡単にコンソール操作可能だったということに気づく。。）
- github actions上で意味がわからないエラーの解消に苦労したり、使用したアクション「google-github-actions/auth@v0」とその後のpush操作でアクセスキーをgithub上にpushしてしまったりした。



### 1. terraformとは？
- Infrastructure as a codeとよばれるソースコードを用いてインフラを構築・管理するサービスの一つ。似たようなものにansibleなどもある。
- terraformを利用すると、クラウドコンソールからポチポチ構築していく手順をソースコードとして管理できる。
- そのため、人手による設定ミスの減らしたり、手順の自動化、他システム構築時にアセットを再利用しやすいといわれている。
- 中身の処理的にはtfstateファイルでクラウドの状態が記述されており、ソースコードと状態ファイルの差分をSDKか何かを経由してデプロイしている？（想像）ため、terraformで管理している状態と現実のクラウド構成で差分が生じると、その差分を解消する必要がある。
- tfstateファイルはs3やGCSなどのクラウドストレージで管理することが一般的だが、一旦の試しでgithub上に保存している。


### 2. github actionsとは？
- githubにおけるワークフロー自動化サービスで、何かのイベントをトリガーに定めた作業をリモートの仮想マシンでやってくれる。
- 今回はソースコードを更新したあとに、terraformで「terraform plan」や[terraform apply」コマンドでクラウドをデプロイする作業を自動化したい。

### 3. 手順

1. サービスアカウントを発行し、組織レベルの権限を付与する
今回は、ResourceManager.ProjectCreator等の権限を付与したい。
まずは、terraformで実行用のフォルダとプロジェクトをコンソールで作成する。
->IAMと管理->リソースを管理からフォルダとプロジェクトを作成する。
![qiita_lac2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3550845/21aee063-664d-af7c-b27f-9e798577e11c.png)
下記リンクを参考にgcloudをインストールする。
https://cloud.google.com/sdk/docs/install?hl=ja#deb
`$ gcloud auth login`
で出力されたURLからgoogleアカウントにログインする。
プロジェクトIDとサービスアカウント名を下記引数に入力し必要な権限を付与する。
`$ gcloud organizations  add-iam-policy-binding <組織ID>  --member="serviceAccount:XXXXXXXXXXX@XXXXXXXX.iam.gserviceaccount.com" --role="roles/resourcemanager.projectCreator" --user-output-enabled false`

2. ローカルPCでterraformをインストールする。(ローカルPCでの検証用のため実施しなくてもOK）
私はWSL2のUbuntuを利用しているため、下記サイトを参考にUbuntu用のコマンドでインストールした
https://developer.hashicorp.com/terraform/downloads
2. サービスアカウントのアクセスキーを発行しておき、terraformでデプロイできるようにする。
サービスアカウントの操作列より、「鍵を管理」を選択、認証鍵をjson形式で発行する。
![qiita_lac3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3550845/42d3e92e-513e-8942-86d1-b1088e4f321f.png)

2. プロジェクト作成とフォルダ作成をterraformで実装する。
terraformは、ブロック単位で記述する。以下はterraformのバージョンや対象のプロバイダー（インフラのプラットフォーム）の設定などを記載する。
```tf 
terraform {
#GCPをデプロイするためのプラグインを定義する
  required_providers {
    google = {
      source = "hashicorp/google"
      version = "4.51.0"
    }
#一意なプロジェクト名で作成したいため、乱数のプラグインを定義する
    random = {
      source = "hashicorp/random"
      version = "3.5.1"
    }
  }
}
#GCP用にリージョンを指定したプロバイダーを定義する
provider "google" {
  region  = "us-central1"
  zone    = "us-central1-c"
}

```
5.  以下でデプロイするリソースを定義できる。
resource <リソースタイプ> <ローカル定義の名前>のブロックでGCPにデプロイしたいサービスを定義する。
``` tf
# フォルダ定義
resource "google_folder" "folder" {
    #組織を指定
    parent       = "organizations/XXXXXXX"
　　#フォルダ名を指定
    display_name = var.project_folder
}

# プロジェクト定義
resource "google_project" "project" {
　　#払い出したいIDの分だけloopするように
    count        = "${length(local.list_values)}"
    name         = "${element(element(local.list_values, count.index),0)}"
    #一意なプロジェクト名のため、乱数を含めて定義
    project_id   = "${element(element(local.list_values, count.index),0)}-${random_integer.project_name[count.index].result}" 
　　#どのフォルダの下にプロジェクトを作るかを定義
    folder_id    = google_folder.folder.id
}

# IAMバインド定義
resource "google_project_iam_binding" "project_iam_binding" {
  count   = "${length(local.list_values)}"
　#どのプロジェクトのどのメンバーに権限を付与するかを記載
  project = "${element(google_project.project, count.index).id}"   
　#付与したいロールを記載
  role    = "roles/editor"

  members = [
    "user:${element(element(local.list_values, count.index),1)}",
  ]
}
```
これらのファイルをコーディングして、下記一連のコマンドを実施すればGCPにリソースがデプロイされる。
が、毎回terraformの手作業も面倒のため、github actionsによる自動化をしてみる。
``` $ terraform init ```
``` $ terraform plan ```
``` $ terraform apply ```

6. github actions実装
./.github/workflows/のディレクトリの下にyamlファイルを配置することで、そのyamlファイルに記載の一連のコマンドを実施してくれる。


```yaml

name: ci-tf-deploy

#githubにソースコードを反映させるのをトリガーにjobsに記載のコマンドを実施
on:
  push:
    branches: master 
  pull_request:
    branches: master 
#ここに一連のコマンドを記載
jobs:
  terraform:
    name: Terraform
    runs-on: ubuntu-latest
    steps:  
     #githubにあるソースコードを仮想マシン上にコピーするてきなやつ
      - name: Checkout
        uses: actions/checkout@v3
     #nodeのバージョンを14にしないとエラーで失敗する。なぜ？
      - uses: actions/setup-node@v2
        with:
          node-version: '14'
　　 #githubに保存したgcpの認証鍵情報を参照してサービスアカウントの権限を借用する
      - name: 'Authenticate to Google Cloud'
        uses: 'google-github-actions/auth@v0'
        with:
          credentials_json: '${{ secrets.GOOGLE_CREDENTIALS }}'
     #仮想マシン上でterraform環境構築する
      - name: Setup Terraform 
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.0.4
     #プロバイダー等インストールする
      - name: Init Terraform
        run: terraform init
      
      - name: Plan Terraform
        run: terraform plan 
     #GCPサービスをデプロイ
      - name: Apply Terraform
        run: terraform apply -auto-approve 
      #更新したtfstateをgithub上にpushする。ほんとはブランチ切ってプルリクするようにしたほうがよい
      - name: setup git config
        run:  git config --global user.email '${{ secrets.MY_GITHUB_EMAIL}}'

      - name: setup git config
        run:  git config --global user.name '${{ secrets.MY_GITHUB_NAME}}'

      - name: Git add files to local branch
        run: git add .

      - name: Git commit 
        run: git commit -m "payout gcp-id and push updated tfstate to remote repos"

      - name: push to Git repos
        run: git push
        env: 
          GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN}}
    
```
余談1
.gitignoreに\*cred*を無視するような記載をしたほうがよい。
なぜならgithub actionsで実施した'google-github-actions/auth@v0'アクションの中で認証鍵を一時ファイルとして生成してしまうので、.gitignoreで工夫しないとgithub上にpushしてしまうというセキュリティインシデントになりそうなことが起きてしまう。
→　同じことをissueとして挙げている人がすでにいました。
https://github.com/google-github-actions/auth/issues/298

余談2
クラウドコンソール上で組織レベルの権限をサービスアカウントに付与する方法。
左上が組織であることを確認して、+アクセス権を付与から、右側に表示される
プリンシパルの追加にサービスアカウント名を、ロールを選択に組織レベルのロールを追加すると権限を付与できる。（最初なんでこんな簡単なことがわからなかったのか疑問。。）
![qiita_lac4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3550845/2facda47-bd6a-730d-5c57-ded6081960d7.png)