WSL(Ubuntu 20.04)でZenn CLIを使いTerraform(Google Cloud)の記事が快適に書けるようになるまで
こんにちは。
環境整備や見た目に時間をかけすぎて本題に入りそこねがちなクラウドエースの吉崎です。
本稿もそうですが、Zenn CLIを使って記事の作成やプレビューを行っています。
しかし、WSL(Ubuntu)環境で Zenn CLI を使って Terraform(Google Cloud)の記事を書くまでの道のりは、難しくもありませんが簡単でもありません。
次回、別の端末で同じ環境を構築するための備忘として、誰か同じ環境を構築して Zenn で記事を書こうと思っている人のため、本稿ではその道のりを記します。
なお、WSL(Ubuntu)のインストールは割愛しますので、WSL(Ubuntu)はご自分で準備してください。
環境
- Windows 11 21H2
- Ubuntu 20.04.4 LTS on WSL2
道のり
リポジトリを更新
インストールされるパッケージが古いものにならないようにリポジトリを更新します。
$ apt update
Node.js と npm をインストールする
# インストール
$ apt install -y nodejs
# バージョン確認
$ nodejs -v
v10.19.0
# インストール
$ apt install -y npm
# バージョン確認
$ npm -v
6.14.4
本稿の執筆時点では node.js v10.19.0
, npm v6.14.4
がインストールされました。
しかし、これらのバージョンは Zenn ClI にサポートされていないので、新しいバージョンをインストールする必要があります。
下記はそのまま Zenn CLI をインストールしたときの警告例です。
$ npm install zenn-cli
npm WARN notsup Unsupported engine for zenn-cli@0.1.125: wanted: {"node":">=14.0.0"} (current: {"node":"10.19.0","npm":"6.14.4"})
Node.js と npm の新しいバージョンをインストールする
- n をインストールする
n は対話的に Node.js のバージョンを管理するツールです。
Python でいう Pyenv です。いや、厳密には Nodenv が Pyenv にあたりますね。
n を使うことで、Node.js のバージョンを簡単に管理出来るようになります。
$ npm install -g n
-g オプションでグローバルにインストールします。
このオプションを付けない場合、作業ディレクトリでのみ有効となります。
- 安定版確認
$ n --stable
16.17.0
--latest
とすると最新バージョンを参照できます。
- 安定バージョンインストール
$ n stable
16.17.0
筆者の環境では上記コマンドがタイムアウトとなったため、以下のように問い合わせ先 DNS サーバを変更したところ進めました。
$ vim /etc/resolv.conf
nameserver 8.8.8.8 # Google Public DNS
nameserver 8.8.4.4 # Google Public DNS
ちなみに、こちらのファイルは WSL を再起動すると消えます。
どうやって残すかは こちらの記事 を参照ください。
改めて Node.js のバージョンを確認します。
$ node --version
v16.17.0
安定版になっていますね。
旧バージョンは不要なのでアンインストールしても構いません。
ちなみに npm は Node.js に同梱されているため、npm のバージョンも上がっています。
$ npm -v
8.15.0
Zenn CLI を使う
- インストールする
$ npm install -g zenn-cli
- 初期化する
記事を管理するディレクトリで init
コマンドを実行します。
$ npx zenn init
🎉 Done!
早速コンテンツを作成しましょう
👇 新しい記事を作成する
$ zenn new:article
👇 新しい本を作成する
$ zenn new:book
👇 投稿をプレビューする
$ zenn preview
絵文字が使われていて見やすいですね。
npx コマンドはローカルにインストールされたモジュールもインストールされていないモジュールも実行出来る便利なコマンドです。
init
を実行したディレクトリは以下のようになります。
$ ls
README.md articles books
Terraform を準備する
公式ページに Ubuntu のインストール手順が記載されていますのでその通りに実行します。
$ wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
$ echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
$ sudo apt update && sudo apt install terraform
というわけで Terraform がインストールされました。
$ terraform version
Terraform v1.3.0
on linux_amd6
gcloud CLI を準備する
こちらも公式ドキュメントの通りコマンドを実行します。
# gcloud CLI のインストール
$ sudo apt-get install apt-transport-https ca-certificates gnupg
$ echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
$ curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add -
$ sudo apt-get update && sudo apt-get install google-cloud-sdk
# gcloud CLI の初期化
$ gcloud init
gcloud init
実行後は、
- ブラウザでリンクを開き、認証コードを取得する
- 認証コードをターミナルに入力する
- プロジェクト ID を設定する
- デフォルトリージョン・ゾーンを選択する
という作業を実施しますが、これらは割愛します。
Application Default Credentials(ADC) を設定する
Terraform で Google プロバイダを使う際、サービスアカウントキーを credentials に指定するのではなく、ADC の認証情報を使うようにします。
これは、セキュリティ上、サービスアカウントキーを出来るだけ作成しないことが望ましいためです。
詳しくはサービスアカウントキーのベストプラクティスを参照ください。
$ gcloud auth application-default login
こちらもリンクをブラウザで開き、認証コードをターミナルに入力する必要があります。
ちなみに実行時の出力を見ると分かりますが、ADC の情報は $HOME/.config/gcloud/application_default_credentials.json
に格納されます。
以下のような内容になっていると思います。(重要そうな部分は x で伏せています)
$ cat $HOME/.config/gcloud/application_default_credentials.json
{
"client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
"client_secret": "xxxxxxxxxxxxxxxxxxxxxxxx",
"quota_project_id": "xxxxxxxxxxxxxxxxxxxxxxxx",
"refresh_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"type": "authorized_user"
}
Terraform の状態を管理するファイル(tfstate)を格納する GCS バケットを作成する
まずは google プロバイダを使って初期化します。
main.tf
を以下の通り作成してください。
provider "google" {
project = "xxxxxxxxxxxxxxxxxxxxxxxx" # ご自身のプロジェクトIDを指定してください
region = "asia-northeast1" # 大阪リージョン
}
terraform init
します。
$ terraform init
以下のような出力となると思います。
これで初期化は OK です。
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/google...
- Installing hashicorp/google v4.37.0...
- Installed hashicorp/google v4.37.0 (signed by HashiCorp)
次に tfstate を管理するための GCS バケットを作成します。
構成は 公式ドキュメント を参考にしています。
では、main.tf
に以下の通り追加してください。
provider "google" {
project = "ca-yoshizaki-test-352704"
region = "asia-northeast2"
}
+ resource "random_id" "instance_id" {
+ byte_length = 8
+ }
+
+ resource "google_storage_bucket" "tfstate" {
+ name = "bucket-tfstate-${random_id.instance_id.hex}"
+ force_destroy = false
+ location = "asia-northeast2"
+ storage_class = "STANDARD"
+ }
そして terraform plan
を実行します。
$ terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# google_storage_bucket.tfstate will be created
+ resource "google_storage_bucket" "tfstate" {
+ force_destroy = false
+ id = (known after apply)
+ location = "ASIA-NORTHEAST2"
+ name = (known after apply)
+ project = (known after apply)
+ self_link = (known after apply)
+ storage_class = "STANDARD"
+ uniform_bucket_level_access = (known after apply)
+ url = (known after apply)
+ versioning {
+ enabled = (known after apply)
}
}
# random_id.instance_id will be created
+ resource "random_id" "instance_id" {
+ b64_std = (known after apply)
+ b64_url = (known after apply)
+ byte_length = 8
+ dec = (known after apply)
+ hex = (known after apply)
+ id = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
terraform apply
でバケットを作成します。
$ terraform apply
~
random_id.instance_id: Creating...
random_id.instance_id: Creation complete after 0s [id=L1tLaaUlhZ4]
google_storage_bucket.tfstate: Creating...
google_storage_bucket.tfstate: Creation complete after 1s [id=bucket-tfstate-2f5b4b69a525859e]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
~
作成されたバケット名はクリップボードに控えておくと良いです。
次にバケットが作成されたことを確認します。
$ gsutil ls | grep bucket-tfstate-2f5b4b69a525859e
gs://bucket-tfstate-2f5b4b69a525859e/
gcloud storage buckets list
でも確認出来ますが、上記の方がシンプルです。
現時点では terraform.tfstate
はローカルにありますので、これを GCS に移行します。
main.tf
に backend
を追加します。
provider "google" {
project = "ca-yoshizaki-test-352704"
region = "asia-northeast2"
}
resource "random_id" "instance_id" {
byte_length = 8
}
resource "google_storage_bucket" "tfstate" {
name = "bucket-tfstate-${random_id.instance_id.hex}"
force_destroy = false
location = "asia-northeast2"
storage_class = "STANDARD"
}
+ terraform {
+ backend "gcs" {
+ bucket = "bucket-tfstate-2f5b4b69a525859e"
+ }
+ }
この状態で terraform init
を実行します。
このコマンドは、ローカルの tfstate を GCS バケットにアップロードしてくれます。
$ terraform init
Initializing the backend...
Do you want to copy existing state to the new backend?
Pre-existing state was found while migrating the previous "local" backend to the
newly configured "gcs" backend. No existing state was found in the newly
configured "gcs" backend. Do you want to copy this state to the new "gcs"
backend? Enter "yes" to copy and "no" to start with an empty state.
Enter a value:
yes
とすると GCS バケットに tfstate がアップロードされます。
$ gsutil ls gs://bucket-tfstate-2f5b4b69a525859e/
gs://bucket-tfstate-2f5b4b69a525859e/default.tfstate
やっと GCS バケットに tfstate を配置出来ました。
新しい記事を書く
後は Zenn 公式 を見て記事を作成してください。
npx zenn new:article
筆者のディレクトリ構成は以下の通りとなっています。
記事作成時にランダムに作成された slug
のディレクトリを terraform
配下に作成し、その中に Terraform コードを書いていっています。
VSCode を使っており、エディター画面で記事を編集し、ターミナルで .tf
ファイルを編集しています。
$ tree -a
.
├── articles
│ ├── 923a505b8a3c59.md
│ └── d0906b43de4a74.md
├── books
└── terraform
├── 923a505b8a3c59
│ └── main.tf
└── d0906b43de4a74
└── main.tf
※一部内容を割愛
特に工夫を凝らしたディレクトリ構成ではありませんので、今後記事が増えると改善されていくでしょう。
まとめ
引っ掛かりポイントは、
- Ubuntu のデフォルトのリポジトリでインストールされる Node.js のバージョンが古い
- Hashicorp の gpg キーを取得するコマンドがタイムアウトする(ことがある?)
- Terraform で Google プロバイダを使用する際に ADC の設定が必要
でした。
この後 GitHub 連携により、push をトリガーに記事が自動で公開されることでしょう。
Discussion