セルフマネージドなk8sクラスターをIaC駆使して自動構築したい。無料で！ → そうだOCI使おう

こちらの記事が面白そうだったので、ハンズオンしてみましたという記事です。
（@gashirarさん、素敵な記事をありがとうございます！！）

https://qiita.com/gashirar/items/a9f73bf945d201c80cfa

上記の元記事ではスマートにまとめてくれている部分も多いですが、シロート気味の私がハンズオンする中で軽くつまづいたり、ググって調べたりしたところもスクショ付きで残しているので、そのあたりがバリューになるかなと思います。


# やりたいこと
勉強用のおうちk8sクラスターを無料で運用したい！
　→ OCIのFree Tierなら無料でIaaS構築＆運用できる
　→ k8sはセルフマネージドならコントロールプレーンも無償運用できそう
　→ プロビジョニングはTerraform＆Ansibleで自動化しちゃおう


# やること
- VCN環境をOCIで準備（Terraform）
- コンピュートインスタンス上にkubeadmでk8sクラスター構築（Ansible）


# やったこと
私はM1 MacBook Airを利用してハンズオンしました。
ターミナルではzshを使用しています。


### OCIコンソールにログイン
私の場合、少し前に触ったまま30日無料枠が終了しており、そのせいか以前ログインできていたIDCS用のサインイン画面だと認証が通らなくなっていていきなりハマりました。
以下はOracle Cloudの中でもOCI専用のサインイン画面となるため、IDCSではなくIAMによる認証が可能なためAlways Freeではこちらを使うのが良いのかなと思われます。

https://cloud.oracle.com/?region=ap-tokyo-1

以下はIDCSのサインイン画面。こっちだと以前使えたIDで認証が通らなかった。
![スクリーンショット 2022-03-12 15.10.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/3d9542f9-e711-e13b-98e2-500812a92d38.png)

メールで届いていた以下の青いボタンから開くサインイン画面で無事にサインインできました。
![スクリーンショット 2022-03-12 15.15.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/241dc0d8-c6a1-0cae-8120-fe1638569c3e.png)

こっちのURLは個人用っぽかったので、URL掲載はやめときます。
![スクリーンショット 2022-03-12 15.16.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/b61ee826-08bc-85ee-fd8f-9e258bad10be.png)

無事にサインイン完了。
![スクリーンショット 2022-03-12 15.30.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/1980f558-246f-f1cf-dd32-b9f27a87c565.png)


### OCI CLIの設定
インストールは簡単でした。ターミナルでhomebrew叩くだけ。

https://docs.oracle.com/ja-jp/iaas/Content/API/SDKDocs/cliinstall.htm

```zsh:ターミナル
brew update && brew install oci-cli
```

初期設定はググりながらやりました。
以下コマンドでダイアログ形式の初期設定が可能です。

```zsh:ターミナル
oci setup config
```

既定値のままで問題ないものはEnterで飛ばし、個別にパラメーター入れる必要があるものはOCIのコンソールより取得します。
- OCIユーザーのOCID
- OCIテナンシーのOCID

このあと、ローカルに作成した公開鍵をOCIコンソールからIAMに設定する必要があります。
以下のページを参考にさせていただきました。

https://www.ashisuto.co.jp/db_blog/article/N0021_OracleCloud_20200626.html

途中でターミナルに出力される鍵のパス等は、あとでTerraformのコードに埋め込んだりする際にコピペして使います。
設定が終わったはずの人は、以下のコマンドを実行してOCI CLIがうまく操作できるか確認してみてください。問題なければ表形式でリージョン一覧が返却されます。

```zsh:ターミナル
oci iam region list --output table
```

#### ハマりポイント
私の場合、401の権限エラーになってしまったのですが、セットアップコンフィグのダイアログ内で大量のリージョンがリストアップされ「この中から選べ！」と言われた際に「ap-chiyoda-1」なるものを東京リージョンと勘違いし選んでしまっていたことが原因でした。
ユーザーディレクトリ配下の .oci/config ファイルを編集し、「ap-tokyo-1」へ修正することで無事にコマンドが通りました。


### コンパートメントの作成
OCIの独自概念。GCPでいう「プロジェクト」のように、アカウント内を論理的に分割しリソースや権限系を別管理しやすくしてくれる機能です。

初期状態で「ルートコンパートメント」も存在していますが、ハンズオン記事に沿って新規コンパートメントをWebコンソールから作成します。
「親コンパートメント」にルートを指定し、子コンパートメントとして作成します。フォルダーのように入れ子にするイメージですね。
![スクリーンショット 2022-03-12 17.12.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/c34e5a28-d5f2-6135-454c-5718c2397bde.png)


### GitHubリポジトリのクローン
元記事では親切なことにサンプルコードを公開してくれています。
初心者目線でやることをリストアップすると以下です。

1. WebブラウザからGitHubアカウントを作成
1. Macにgitをインストールし初期設定
1. git cloneでリポジトリをローカルにコピー
1. VScodeなどのコードエディターでコピーした資材を開く

gitの初期設定はSSH鍵のパーミッション周りでつまづく人が多いと思うのですが、GitHubからのクローン時はSSHだけでなくHTTPS方式も選択できるため、こちらのURLを利用してクローンすればSSH設定は不要になります。
　※gitが難しければ最悪、ZIP形式でブラウザからそのままダウンロードできます。
![スクリーンショット 2022-03-12 17.48.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/fa9360a8-a4e7-ff3e-a764-ec8ac0bff45d.png)


### Terraform設定ファイルの作成
サンプルファイル　`terraform/main.tfvars.example` をコピーして編集します。
![スクリーンショット 2022-03-12 18.55.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/91546bcf-8353-ce52-52a1-226a923ff9f2.png)


### IaCツールの導入
以下をMacにインストールしておきます。
- Terraform v0.12
- jq v1.6
- Ansible v2.9

いずれもHomebrewでインストール可能です。
TerraformとAnsibleは明示的なバージョン指定が必要でした。
`brew info <ツール名>` で最新バージョンが確認可能なのと、 `brew install <ツール名@バージョン>` でバージョンを指定したインストールが可能です。
インストール後は出力メッセージに従ってパスを通すのを忘れないようにしましょう。

Terraformはバージョン毎の仕様変更が激しく、元記事の執筆タイミング付近のバージョンをインストールしないとスクリプトがエラーを吐いてしまいます。
「tfenv」を使うとバージョン管理が便利なんですが、M1 Macだとv1.0.2より前のバージョンはインストールに失敗するようで、brewで直接インストールしてしまうのが簡単です。

```zsh:ターミナル
brew install --build-from-source terraform@0.12
echo 'export PATH="/opt/homebrew/opt/terraform@0.12/bin:$PATH"' >> ~/.zshrc
```

※追記したパス設定を有効化するため、ターミナルの再起動が必要になります。


### OCIリソースの作成
クローンしたリポジトリ内のシェルスクリプトを実行すればTerraformが動きます。

```zsh:ターミナル
./bin/terraform.sh
```

#### ハマりポイント1
TerraformのOCIプロバイダーから以下のようなエラーが出ました。

```hcl:Terraform
Error: 400-InvalidParameter 
Provider version: 4.67.0, released on 2022-03-10.  
Service: Core Instance 
Error Message: Invalid ssh public key type "-----BEGIN" 
Suggestion: Please Update the parameter(s) in the Terraform config as per error message Invalid ssh public key type "-----BEGIN"
```

ググると「RSAキーペアじゃなくてSSHキーペアを使え」的なQAが見つかりました。
（これは一般的な知識？？ITリテラシー低くて分かりませんでした）

https://webcache.googleusercontent.com/search?q=cache:LEfNsnEKzV4J:https://github.com/terraform-providers/terraform-provider-oci/issues/122+&cd=2&hl=ja&ct=clnk&gl=jp&client=safari

とりあえず `ssh-keygen` コマンドを実行してMac上でSSHキーペアを作成し、その `id_rsa.pub` ファイルのパスを `main.tfvars` ファイル内に記載すると上手くいきました。

#### ハマりポイント2
今度は次のエラーが出ました。

```hcl:Terraform
Error: 400-LimitExceeded 
Provider version: 4.67.0, released on 2022-03-10.  
Service: Core Instance 
Error Message: The following service limits were exceeded: standard-e2-micro-core-count. Request a service limit increase from the service limits page in the console.  
Suggestion: Request a service limit increase for this resource Core Instance
```

なんかサービスリミットを超過した的なことが書いてあるので、もしかすると以前にいじった際に消せてないインスタンスがあってAlways Free枠の制限に掛かっているのではと推測。
コンピュートのコンソールを見てみると確かに1台残っていたため、「インスタンスの終了」を実施。

※ちなみにOCIのWebコンソールって、コンピュートインスタンス画面で「コンパートメント」を選択しないと一覧表示されないようです。
![スクリーンショット 2022-03-12 20.27.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/d33faf4c-4f19-08bc-68d4-021db2a9d2b4.png)

上記のハマりポイント2点を乗り越え、無事にterraform apply完了！
途中で何度もエラーでコケてリトライしましたが、いちいちお掃除しなくても成功してくれるのは冪等性が担保されているIaCならではですね。


### k8sクラスターの作成
GitHubリポジトリーを見ると、クラスター導入ツールが2オプション（kubeadmもしくはk3s）用意されているようです。
元記事の時点ではkubeadmが利用されていたようですね。こちらはk8sのベストプラクティスに沿って愚直にクラスターを構築してくれるツールのようですが、一方でk3sは超軽量なクラスター作成ツールで、minikube等と並んでおうち実験用クラスター作成などによく使われているようです。

https://www.padok.fr/en/blog/minikube-kubeadm-kind-k3s

今回は勉強のためにもkubeadmを利用します。
まず `ansible-kubeadm` ディレクトリーに `hosts.ini` が作成されているか確認しましょう。
存在していない場合、先ほど実行したシェルでTerraformがコケているか、最後に実行されるjqのインストールを忘れている等が考えられます。
![スクリーンショット 2022-03-12 21.02.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/194be5ae-9c77-896b-5a76-cc201827a530.png)

上記問題なければ、 `./bin/setup-by-kubeadm.sh` を実行すればAnsibleが走ってkubeadmがクラスターを構築してくれます。
（けっこう時間かかります）
![スクリーンショット 2022-03-12 21.13.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/6c997535-16d3-83ad-8df1-0bd70d92ccf6.png)

そして私の場合、最後の方のネットワークコンポーネント作成でエラーを吐いてしまいました。
ざっと見た感じ、k8sリソースの中に最近のバージョンでは非推奨になってしまったものは混じっているためのように見えます。Terraformもですがk8sも短期間での仕様変更がかなり激しいですね。

ホントにクラスター作成失敗してるのかよく分からないので、とりあえず動作確認までは進んでみます。

### k8s操作用の設定ファイルを配置したら完了！
手元のMacからk8sクラスターにコマンドを発行できるよう、kubectlコマンドをインストールしましょう。

https://kubernetes.io/ja/docs/tasks/tools/install-kubectl/

そして今回作成したOCI上のk8sクラスターに命令できるよう、kubectlの設定ファイルを配置します。実は先ほど実行したAnsible用シェルの最後に設定ファイルがMacのホームディレクトリーに吐かれているので、以下のコマンドで移動します。

```zsh:ターミナル
mv ~/admin.conf ~/.kube/config
```

これでkubectlする準備が整っているはず…。


### おそるおそるk8sクラスターを操作してみる
まずはノードの確認。

```zsh:ターミナル
%kubectl get nodes
NAME             STATUS   ROLES                  AGE   VERSION
handson-master   Ready    control-plane,master   19m   v1.23.4
```

ちゃんとコマンドは通りましたが、マスターノードしかいませんね。怪しさ満点。
ダメ元でPodをデプロイしてみます。まずPodがいないことを確認

```zsh:ターミナル
%kubectl get pods
No resources found in default namespace.
```

そして適当なdeploymentを書いてデプロイしてみます。
サンプルファイルは手前味噌ですが、以下の記事にて利用したnginxコンテナを3セット展開するyamlを作成。

https://qiita.com/minorun365/items/0441e4878f0984a9fc0a

```zsh:ターミナル
%kubectl apply -f deployment.yaml 
deployment.apps/triple-nginx created

%kubectl get pods                
NAME                            READY   STATUS    RESTARTS   AGE
triple-nginx-5c6777df98-flh9t   0/1     Pending   0          5s
triple-nginx-5c6777df98-rw2gv   0/1     Pending   0          5s
triple-nginx-5c6777df98-sf7dg   0/1     Pending   0          5s
```

applyはうまく通ったんですが、Podが全部ペンディングになりました。
ログを確認してみます。

```zsh:ターミナル
%kubectl logs triple-nginx-5c6777df98-flh9t
（中略）
Events:
  Type     Reason            Age                  From               Message
  ----     ------            ----                 ----               -------
  Warning  FailedScheduling  16s (x3 over 2m38s)  default-scheduler  0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.
```

マスターノードにPodをデプロイしようとしているため、taintに引っかかってPodが起動失敗しているようです。


### Ansibleエラーの再確認
長いエラーをちゃんと見直してみると、この辺が致命的なようです。

```zsh:ターミナル
unable to recognize \"/etc/kubernetes/network/flannel.yaml\": no matches for kind \"ClusterRole\" in version \"rbac.authorization.k8s.io/v1beta1\"
unable to recognize \"/etc/kubernetes/network/flannel.yaml\": no matches for kind \"ClusterRoleBinding\" in version \"rbac.authorization.k8s.io/v1beta1\" 
```


### k3sでリベンジ！
kubeadm用のAnsibleプレイブックをデバッグするのは私のスキルでは荷が重いので、もう一つのk3sオプションを試してみようと思います。

面倒ですが、 `./bin/destroy.sh` で一度環境破壊を行います。
無事にterraform destroy完了したら、 `./bin/terraform.sh` でOCI環境を再構築します。

※余談ですが、これまでIaCってWebコンソールでポチポチやるより難しいので一長一短だな〜などと思ったりもしていましたが、こういうことをやっていると破壊と構築を一瞬で繰り返せるので非常に便利ですね。

IaaS環境ができたら、リベンジ用のk3s構築スクリプトは `./bin/setup-by-k3s.sh` です。

![スクリーンショット 2022-03-12 22.07.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/1d6d7d19-73f0-e6c6-3dea-242a27a941f4.png)

こっちはエラーなく完了！インストレーションも一瞬でした。さすがk3s。
そしてもう一度、kubeconfig設定ファイルを更新します。

```zsh:ターミナル
mv ~/admin.conf ~/.kube/config
```


### k3sクラスターを触ってみる
今度は上手くいくはず？ノード情報を取得してみます。

```zsh:ターミナル
%kubectl get nodes
NAME             STATUS   ROLES                  AGE     VERSION
handson-master   Ready    control-plane,master   7m15s   v1.22.7+k3s1
handson-worker   Ready    <none>                 6m13s   v1.22.7+k3s1
```

マスター＆ワーカーとも取得できています。
ちなみにAnsible実行直後はkubectlがフリーズしてしまったのですが、数分時間を置くとうまくいきました。クラスターの初期化途中だったのかも知れません。

先ほどと同様、deploymentを適用してみます。

```zsh:ターミナル
%kubectl apply -f deployment.yaml 
Error from server (InternalError): error when creating "deployment.yaml": Internal error occurred: resource quota evaluation timed out
```

リソースクォータの評価でタイムアウトしてしまった様子。
スペックの問題？deploymentをやめて単一のpodをデプロイしてみます。

```zsh:ターミナル
%kubectl apply -f pod.yaml 
Unable to connect to the server: net/http: request canceled (Client.Timeout exceeded while awaiting headers)
```

今度は接続自体がタイムアウトしてしまったようです。
ネットワークの問題でしょうか？

OCIのWebコンソールからインスタンスを覗いてみると…
![スクリーンショット 2022-03-12 22.24.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/6279b391-7e18-9be9-b2cf-e2655cdc067e.png)

げっ、マスターノードのメモリー使用率が100%近くに張り付いています。これが原因かも知れません。
なかなか調子が戻らなさそうなので、もう一度環境破壊→再構築をやり直してみましたが、同じく起動直後にメモリー張り付きが再現し、kubectlがほぼ通らなくなってしまいました。残念。


# まとめ
ということでかなり消化不良感がありますが、Always Free枠で運用できるインスタンスだとリソース面がかなりカツカツなのかも？

↓ ちなみに後日、今回の学びをOCIjpでもLTさせていただきました！

https://speakerdeck.com/minorun365/seruhumanezidonak8shuan-jing-wo-iacqu-shi-sitezi-dong-gou-zhu-sitai-wu-liao-de-nil-soudaocishi-ou