Packer on GCP: Embulkイメージの作成

# これはなんですか

- PackerでCompute Engineのマシンイメージを作ってみたので、作業記録を残します。
- Docker/Kubernetes の時代になって、初めてイメージを焼きました。

# つくりたいもの

- Embulkが動作するGCE VM Machine Image
- Embulk用にユーザーを作成する
- とりあえずsudoerが `sudo su - embulk` でユーザーを切り替えられる様な状態にする
    - 発展形として、外部ユーザーが `embulk` コマンドの実行やプラグインのインストールをできるようにしたい。

# 作業記録

## 作業環境

- MacBook Pro
    - `brew install packer` でPackerをインストールする
- Cloud Shell
    - `gcloud project config set [YOUR PROJECT NAME]` でプロジェクトを指定しておく。

## サービスアカウント作成

Packer の Google Compute Builder ページ
https://www.packer.io/docs/builders/googlecompute.html

Cloud IAM 役割について
https://cloud.google.com/iam/docs/understanding-roles

雑に↓を振ってみる

- roles/compute.networkUser
- roles/compute.instanceAdmin.v1
- roles/iam.serviceAccountUser

Cloud Shell上でサービスアカウント `packer` を作成する。
https://cloud.google.com/iam/docs/creating-managing-service-accounts?hl=ja#iam-service-accounts-create-gcloud

```shell
$ gcloud beta iam service-accounts create packer --description "Allow to build Compute Engine images (for Packer)"
```

作成したサービスアカウントに役割を付与する。
https://cloud.google.com/iam/docs/granting-roles-to-service-accounts?hl=ja#granting_access_to_a_service_account_for_a_resource

```shell
$ gcloud projects add-iam-policy-binding [YOUR_PROJECT_ID] --member serviceAccount:packer@[YOUR_PROJECT_ID].iam.gserviceaccount.com --role roles/compute.networkUser
$ gcloud projects add-iam-policy-binding [YOUR_PROJECT_ID] --member serviceAccount:packer@[YOUR_PROJECT_ID].iam.gserviceaccount.com --role roles/compute.instanceAdmin.v1
$ gcloud projects add-iam-policy-binding [YOUR_PROJECT_ID] --member serviceAccount:packer@[YOUR_PROJECT_ID].iam.gserviceaccount.com --role roles/iam.serviceAccountUse
```

## キーの発行

サービスアカウントはできたので、キーを発行する。Cloud Shell 上でキーを発行してローカルに落とす。
https://cloud.google.com/iam/docs/creating-managing-service-account-keys?hl=ja#creating_service_account_keys

```shell
$ gcloud iam service-accounts keys create ~/key.json \
  --iam-account packer@[YOUR_PROJECT_ID].iam.gserviceaccount.com
created key [0fa7b041fb670319ec3400f0432e7f0e13af4425] of type [json] as [/home/username/key.json] for [packer@[YOUR_PROJECT_ID].iam.gserviceaccount.com]
```

## Packer ファイル作成

まとめた。
https://github.com/Hikosaburou/packer_gce_embulk

```json:variables.json
{
    "project_id": "[YOUR_PROJECT_ID]",
    "image_family": "debian-9",
    "account_file_path": "{{ env `GOOGLE_APPLICATION_CREDENTIALS` }}"
}
```

```json:embulk.json
{
    "builders": [
        {
            "type": "googlecompute",
            "account_file": "{{user `account_file_path`}}",
            "project_id": "{{user `project_id`}}",
            "source_image_family": "{{user `image_family`}}",
            "ssh_username": "packer",
            "zone": "asia-northeast1-a",
            "image_family": "{{user `image_family`}}",
            "image_name": "embulk-{{user `image_family`}}-{{timestamp}}"
        }
    ],
    "provisioners": [
        {
            "type": "shell",
            "script": "install_embulk.sh"
        }
    ]
}
```

```sh:install_embulk.sh
#!/bin/bash -ex

sleep 5
sudo sed -e 's/APT::Periodic::Update-Package-Lists "1";/APT::Periodic::Update-Package-Lists "0";/' \
  -e 's/APT::Periodic::Unattended-Upgrade "1";/APT::Periodic::Unattended-Upgrade "0";/' \
  /etc/apt/apt.conf.d/20auto-upgrades -i

sudo apt-get update -y
sudo apt-get install default-jdk -y

sudo adduser --disabled-password --gecos '' embulk

sudo curl --create-dirs -o /home/embulk/.embulk/bin/embulk --silent -L "https://dl.embulk.org/embulk-latest.jar"
sudo chmod +x /home/embulk/.embulk/bin/embulk
echo 'export PATH="$HOME/.embulk/bin:$PATH"' >> ~/tmp_bashrc
sudo sh -c "cat ${HOME}/tmp_bashrc >> /home/embulk/.bashrc"
sudo chown -R embulk:embulk /home/embulk/
rm ~/tmp_bashrc

sudo su - embulk <<EOF
/home/embulk/.embulk/bin/embulk gem install embulk-input-sqlserver
/home/embulk/.embulk/bin/embulk gem install embulk-output-bigquery
EOF
```

install_embulk.sh内の `sudo apt-get update -y` はなんとも微妙。。。でも進める。

Packer コマンドでローカルからビルドする:

```shell
$ packer build --var-file=variables.json embulk.json
googlecompute output will be in this color.

==> googlecompute: Checking image does not exist...
==> googlecompute: Creating temporary SSH key for instance...
==> googlecompute: Using image: debian-10-buster-v20191121
==> googlecompute: Creating instance...
    googlecompute: Loading zone: asia-northeast1-a
    googlecompute: Loading machine type: n1-standard-1
    googlecompute: Requesting instance creation...
    googlecompute: Waiting for creation operation to complete...
    googlecompute: Instance has been created!
==> googlecompute: Waiting for the instance to become running...
    googlecompute: IP: 34.84.207.9
==> googlecompute: Using ssh communicator to connect: 34.84.207.9
==> googlecompute: Waiting for SSH to become available...
==> googlecompute: Connected to SSH!
==> googlecompute: Provisioning with shell script: install_embulk.sh
==> googlecompute: + sleep 5
==> googlecompute: + sudo sed -e 's/APT::Periodic::Update-Package-Lists "1";/APT::Periodic::Update-Package-Lists "0";/' -e 's/APT::Periodic::Unattended-Upgrade "1";/APT::Periodic::Unattended-Upgrade "0";/' /etc/apt/apt.conf.d/20auto-upgrades -i

...

==> googlecompute: Deleting instance...
    googlecompute: Instance has been deleted!
==> googlecompute: Creating image...
==> googlecompute: Deleting disk...
    googlecompute: Disk has been deleted!
Build 'googlecompute' finished.

==> Builds finished. The artifacts of successful builds are:
--> googlecompute: A disk image was created: embulk-debian-9-1575458143
```

通った。

# 失敗の記録

主にシェルスクリプト力の欠如による失敗の記録。

## 実行するコマンドも出力したい

Shell Provisionerのドキュメントに書いてあった。
https://www.packer.io/docs/provisioners/shell.html

スクリプトのシェバン(shebang)に `-x` をつけよ、とのこと。

```sh:embulk_install.shのシェバン
#!/bin/bash -ex
```

`inline` パラメータでコマンドを渡している場合は `inline_shebang` で渡してあげれば良いらしい。

## curl の出力を省略したい

`--silent` オプションをつければ、↓みたいなのが出なくなる.

```:build出力
==> googlecompute:   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
==> googlecompute:                                  Dload  Upload   Total   Spent    Left  Speed
==> googlecompute:   0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
==> googlecompute:   0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
==> googlecompute: 100   607    0   607    0     0   1498      0 --:--:-- --:--:-- --:--:--  5232
==> googlecompute: 100 43.2M  100 43.2M    0     0  10.3M      0  0:00:04  0:00:04 --:--:-- 15.4M
```

この辺でエラーが出ていたせいで惑わされた。

## `adduser` の対話式インターフェースをスキップしたい

当初はこうやっていた。

```sh
yes '' | sudo adduser --disabled-password embulk
```

けど、もっとちゃんとしたオプションをask ubuntuに教えてもらったので、それに切り替えた。
https://askubuntu.com/questions/94060/run-adduser-non-interactively

```sh
sudo adduser --disabled-password --gecos '' embulk
```

## `sudo su - embulk` でユーザーが切り替えられない

Packerは`packer`ユーザーとしてシェルスクリプトを実行する。途中 `embulk` ユーザーに切り替えてプラグインをインストールするところでこけた

```sh:失敗した箇所
sudo su - embulk
/home/embulk/.embulk/bin/embulk gem install embulk-input-sqlserver
/home/embulk/.embulk/bin/embulk gem install embulk-output-bigquery
```

```:build出力
==> googlecompute: + sudo su - embulk
    googlecompute: -rwxr-xr-x 1 embulk embulk 45378569 Dec  4 11:03 embulk
==> googlecompute: + id
    googlecompute: uid=1000(packer) gid=1001(packer) groups=1001(packer),4(adm),30(dip),44(video),46(plugdev),1000(google-sudoers)
```

上記はスクリプトに `id` をかませている。

無知な自分は、これの回避方法をテラテイル様に教えていただいた。
https://teratail.com/questions/138081

`sudo su - username` にリダイレクトをかけるか、`sh -u username -c "..." ` で引数としてスクリプトを渡せば良いらしい。

```sh:修正後
sudo su - embulk <<EOF
/home/embulk/.embulk/bin/embulk gem install embulk-input-sqlserver
/home/embulk/.embulk/bin/embulk gem install embulk-output-bigquery
EOF
```

`/home/embulk/.bashrc` に `PATH` の更新をかけてたから `embulk` コマンドで通せるかと一瞬思ったけど、当然ダメだった。ので、フルパスを渡している。

## `[ERROR] Embulk does not support Java 11 yet.` というエラー

新しい Debian イメージ `debian-10` を設定して実行したら、↑のエラーが出た。`sudo apt-get install default-jdk -y` で入るJavaのバージョンが新しすぎたためにエラーとなっていた。(.shの中で `java -version` 実行して確認した。。。 )

`apt-get` でバージョン指定するのが良い。けど、とりあえず `debian-9` を指定してお茶を濁した。ただ、`sudo apt-get update -y` が入ってる時点で結末は見えている。。。

# 感想

- Packer 自体はシンプルで簡単だったが、シェルスクリプト力が足りず苦しんだ。
- GCEは爆速でインスタンスが立ち上がるのでAMI焼きよりは楽だったと思う。