[Oracle Cloud] K3sでAlways Free枠ノードにKubernetesクラスタ作成してローカルからkubectlアクセスまで

本記事は「[Oracle Cloud Infrastructure #2 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/oci2)」の13日目のエントリになります。
前日の夜中になんとなく試してみたら意外とすんなり動いたのでまとめてみたら、アドベントカレンダーに空きがあったので滑り込んでみました。

[K3s](https://k3s.io/)を使って[Oracle Cloud](https://www.oracle.com/jp/cloud/)のAllways Free枠の範囲でKubernetesクラスタを作ってみて、手元の環境の`kubectl`からアクセスするまでを確認してみました。
クラスタ上のアプリケーションpodへの外部(インターネット経由)アクセスは未確認です。

あと、現状まだ全て手作業でやってます。
(内容的には全部自動化できそう)

---

更新履歴

- 2021.02.09: 12月時点の内容だと、CNIとして使用するFlannelに必要なポート設定が許可されておらず、ノード間・Pod間で通信ができていませんでした。ノード間で`8472/UDP`の許可と`cni0`インタフェースの`trusted`ゾーン設定の追加が必要です([参考](https://zaki-hmkc.hatenablog.com/entry/2020/03/19/191534#flannel1-cni0%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9%E3%82%92trusted%E3%82%BE%E3%83%BC%E3%83%B3%E3%81%AB%E8%A8%AD%E5%AE%9A))。(なお、この時点でK3sをインストールするとKubernetesバージョンは1.20.2)

# Always Free枠とK3s

Oracle Cloudでは、アカウント作成時に1か月利用できる$300分のクレジットがもらえて自由に使えますが、それ以降も「Always Free対象」として、12か月とかそんな制限なく、期限なしで1CPU,RAM1GBのコンピュートインスタンス(VM)を2台まで作ることができます。

[【AWS, GCP, Azure, OracleCloud, Firebase】無料で使えるクラウドのサーバリソースまとめ【2020年1月版】 - Qiita](https://qiita.com/goyae/items/196221169391dd44125d)

メモリ1GBだとKubernetesを動かすには要件を満たさないのですが、軽量Kubernetesディストリビューションの一つである[K3s](https://k3s.io/)であれば、ノードの要件が以下の通りなので、一応動作します。

- RAM: 最小512MB
- CPU: 最低1CPU

# ノードになるコンピュートインスタンスを作成する

インスタンスの名前は`kubectl get node`のときのノード名になるので、わかりやすいように`node1`, `node2`にします。(が、気にしないなら何でもよいです)

他は特に注意点はなくデフォルトで大丈夫です。
ネットワーキングは既存のものがあればそれを使ってよいですが、2台目の作成時には1台目と同じネットワークを選択します。
(既存のネットワーキングが無い場合は自動で作成される設定になります。その場合の2台目作成時は1台目で作成されたネットワーキングがデフォルトで選択されるので、特に問題ないはず)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/226335bc-01ab-4712-caad-edb0df16e092.png)

SSHアクセスして作業するので、SSHキーは新規作成するなり既存の利用中のものを選択するなりしてください。
ブートボリュームや拡張オプションは何も選択しません。

準備できたら画面下部の「作成」押下します。

インスタンスの一覧画面に戻ると「プロビジョニング中」になるので、しばらく待てば「実行中」になり、VMが起動して接続できるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/9c3a3b6b-1a49-c067-40de-1ff71d45b14f.png)

「実行中」になったら詳細画面でパブリックIPアドレスを確認できるので、SSHしてみます。
SSHの際のユーザー名は`opc`です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/bdc8c11c-4664-6d18-341a-e05b5a390029.png)

```
[zaki@cloud-dev ~]$ ssh opc@---.---.---.--- -i ~/.ssh/id_rsa_oracle
The authenticity of host '---.---.---.--- (---.---.---.---)' can't be established.
ECDSA key fingerprint is SHA256:HA3aRobDFbiyDLCRGmZefr84BAtdj6Su4kaapCrTpYo.
ECDSA key fingerprint is MD5:8f:bd:7a:0a:69:cf:80:5e:cc:b0:fc:74:f9:df:79:ed.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '---.---.---.---' (ECDSA) to the list of known hosts.
[opc@node1 ~]$ df -h
ファイルシス   サイズ  使用  残り 使用% マウント位置
devtmpfs         315M     0  315M    0% /dev
tmpfs            345M     0  345M    0% /dev/shm
tmpfs            345M  5.0M  340M    2% /run
tmpfs            345M     0  345M    0% /sys/fs/cgroup
/dev/sda3         39G  2.9G   36G    8% /
/dev/sda1        200M  8.6M  192M    5% /boot/efi
tmpfs             69M     0   69M    0% /run/user/0
tmpfs             69M     0   69M    0% /run/user/994
tmpfs             69M     0   69M    0% /run/user/1000
[opc@node1 ~]$ free -h
              total        used        free      shared  buff/cache   available
Mem:           688M        319M         87M        1.3M        282M        260M
Swap:          8.0G         40M        8.0G
[opc@node1 ~]$ cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.9 (Maipo)
[opc@node1 ~]$ cat /etc/oracle-release
Oracle Linux Server release 7.9
```

`.ssh/config`に設定追加しておくと`ssh oci-node1`だけで済むので便利です。

```
Host oci-node1
    Hostname ---.---.---.---
    User opc
    IdentityFile ~/.ssh/id_rsa_oracle
    ServerAliveInterval 60
```

確認出来たら、同じ要領で2台目のVMインスタンスも作成します。
(今度は`node2`で作成)

# K3sのセットアップ

[日本語版K3sマニュアル ～Rancher K3s公式ドキュメントより～](https://rancher.co.jp/pdfs/K3s-eBook4Styles0507.pdf)

これの「第3章 クイックスタートガイド」の通り進めます。

## Control Planeノード(@ node1)

※ 便宜上Control Planeと表記してますが、デフォルトでは「オールインワンのノード」となっているため、アプリケーションpodもデプロイされます

まずnode1へK3sを通常インストールします。
「3.1 インストールスクリプト」の下記コマンドを実行します。

```
$ curl -sfL https://get.k3s.io | sh -
```

```
[opc@node1 ~]$ curl -sfL https://get.k3s.io | sh -
[INFO]  Finding release for channel stable
[INFO]  Using v1.19.4+k3s1 as release
[INFO]  Downloading hash https://github.com/rancher/k3s/releases/download/v1.19.4+k3s1/sha256sum-amd64.txt
[INFO]  Downloading binary https://github.com/rancher/k3s/releases/download/v1.19.4+k3s1/k3s
[INFO]  Verifying binary download
[INFO]  Installing k3s to /usr/local/bin/k3s
読み込んだプラグイン:langpacks, ulninfo
ol7_UEKR6                                                | 2.8 kB     00:00     
ol7_addons                                               | 2.8 kB     00:00     
ol7_developer                                            | 2.8 kB     00:00   

:
:

パッケージ yum-utils-1.1.31-54.0.1.el7_8.noarch はインストール済みか最新バージョンです
何もしません
読み込んだプラグイン:langpacks
読み込んだプラグイン:langpacks, ulninfo
rancher-k3s-common-stable                                | 2.9 kB     00:00     
rancher-k3s-common-stable/primary_db                       | 1.8 kB   00:00     
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ k3s-selinux.noarch 0:0.2-1.el7_8 を インストール
--> 依存性の処理をしています: container-selinux >= 2.107-3 のパッケージ: k3s-selinux-0.2-1.el7_8.noarch
--> トランザクションの確認を実行しています。
---> パッケージ container-selinux.noarch 2:2.119.2-1.911c772.el7_8 を インストール
--> 依存性解決を終了しました。

依存性を解決しました

================================================================================
 Package       アーキテクチャー
                      バージョン                リポジトリー               容量
================================================================================
インストール中:
 k3s-selinux   noarch 0.2-1.el7_8               rancher-k3s-common-stable  13 k
依存性関連でのインストールをします:
 container-selinux
               noarch 2:2.119.2-1.911c772.el7_8 ol7_developer              39 k

トランザクションの要約
================================================================================
インストール  1 パッケージ (+1 個の依存関係のパッケージ)

総ダウンロード容量: 53 k
インストール容量: 123 k
Downloading packages:
(1/2): container-selinux-2.119.2-1.911c772.el7_8.noarch.rp |  39 kB   00:00     
warning: /var/cache/yum/x86_64/7Server/rancher-k3s-common-stable/packages/k3s-selinux-0.2-1.el7_8.noarch.rpm: Header V4 RSA/SHA1 Signature, key ID e257814a: NOKEY
k3s-selinux-0.2-1.el7_8.noarch.rpm の公開鍵がインストールされていません
(2/2): k3s-selinux-0.2-1.el7_8.noarch.rpm                  |  13 kB   00:00     
--------------------------------------------------------------------------------
合計                                                65 kB/s |  53 kB  00:00     
https://rpm.rancher.io/public.key から鍵を取得中です。
Importing GPG key 0xE257814A:

:
:

インストール:
  k3s-selinux.noarch 0:0.2-1.el7_8                                              
依存性関連をインストールしました:
  container-selinux.noarch 2:2.119.2-1.911c772.el7_8                            
完了しました!
[INFO]  Creating /usr/local/bin/kubectl symlink to k3s
[INFO]  Creating /usr/local/bin/crictl symlink to k3s
[INFO]  Creating /usr/local/bin/ctr symlink to k3s
[INFO]  Creating killall script /usr/local/bin/k3s-killall.sh
[INFO]  Creating uninstall script /usr/local/bin/k3s-uninstall.sh
[INFO]  env: Creating environment file /etc/systemd/system/k3s.service.env
[INFO]  systemd: Creating service file /etc/systemd/system/k3s.service
[INFO]  systemd: Enabling k3s unit
Created symlink from /etc/systemd/system/multi-user.target.wants/k3s.service to /etc/systemd/system/k3s.service.
[INFO]  systemd: Starting k3s
[opc@node1 ~]$ 
```

こんな感じでK3sの実行ファイル類のインストールと必要な`yum install`が自動で行われます。


```
[opc@node1 ~]$ systemctl status k3s
● k3s.service - Lightweight Kubernetes
   Loaded: loaded (/etc/systemd/system/k3s.service; enabled; vendor preset: disabled)
   Active: active (running) since 日 2020-12-13 02:01:04 GMT; 2min 0s ago
     Docs: https://k3s.io
  Process: 14164 ExecStartPre=/sbin/modprobe overlay (code=exited, status=0/SUCCESS)
  Process: 14160 ExecStartPre=/sbin/modprobe br_netfilter (code=exited, status=0/SUCCESS)
 Main PID: 14171 (k3s-server)
    Tasks: 67
   Memory: 305.9M
   CGroup: /system.slice/k3s.service
           ├─14191 containerd 
           ├─14880 /var/lib/rancher/k3s/data/3a24132c2ddedfad7f599daf636840e8...
           ├─14881 /var/lib/rancher/k3s/data/3a24132c2ddedfad7f599daf636840e8...
           ├─14885 /var/lib/rancher/k3s/data/3a24132c2ddedfad7f599daf636840e8...
           └─14886 /var/lib/rancher/k3s/data/3a24132c2ddedfad7f599daf636840e8...
           ‣ 14171 /usr/local/bin/k3s server

12月 13 02:02:54 node1 k3s[14171]: I1213 02:02:54.974435   14171 topology_...06
12月 13 02:02:55 node1 k3s[14171]: I1213 02:02:55.973536   14171 topology_...e7
12月 13 02:02:55 node1 k3s[14171]: E1213 02:02:55.975309   14171 pod_workers...
12月 13 02:02:57 node1 k3s[14171]: I1213 02:02:57.260614   14171 topology_...06
12月 13 02:02:57 node1 k3s[14171]: I1213 02:02:57.264306   14171 topology_...76
12月 13 02:02:57 node1 k3s[14171]: E1213 02:02:57.266530   14171 pod_workers...
12月 13 02:02:59 node1 k3s[14171]: E1213 02:02:59.669628   14171 resource_...st
12月 13 02:03:01 node1 k3s[14171]: I1213 02:03:01.974345   14171 topology_...b6
12月 13 02:03:03 node1 k3s[14171]: I1213 02:03:03.782379   14171 request.g...2s
12月 13 02:03:04 node1 k3s[14171]: W1213 02:03:04.647762   14171 garbageco...t]
Hint: Some lines were ellipsized, use -l to show in full.
[opc@node1 ~]$ 
```

インストールからクラスタの起動まで全自動。

```
[opc@node1 ~]$ which kubectl
/usr/local/bin/kubectl
[opc@node1 ~]$ kubectl version
WARN[2020-12-13T02:04:03.444091986Z] Unable to read /etc/rancher/k3s/k3s.yaml, please start server with --write-kubeconfig-mode to modify kube config permissions 
error: error loading config file "/etc/rancher/k3s/k3s.yaml": open /etc/rancher/k3s/k3s.yaml: permission denied
[opc@node1 ~]$ sudo /usr/local/bin/kubectl version --short
Client Version: v1.19.4+k3s1
Server Version: v1.19.4+k3s1
[opc@node1 ~]$ sudo /usr/local/bin/kubectl get node
NAME    STATUS   ROLES    AGE     VERSION
node1   Ready    master   3m20s   v1.19.4+k3s1
```

KUBECONFIGが`/etc/rancher/k3s/k3s.yaml`固定になっているっぽく、rootでないとアクセスできないけど、この通り。

## Workerノード(@ node2)

次にnode2をworkerとしてクラスタに追加します。
p14「ワーカーノードにK3sをインストールしてクラスターに追加するには」以降を参照

### セキュリティ設定

OracleCloudのデフォルトだと、同じネットワーク内のコンピュート同士でも通信が制限されているので、許可設定を追加します。(`ping`も通らない)

メニューの「ネットワーキング」->「仮想クラウド・ネットワーク」から。
![2020-12-13_11h13_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/51eb1b12-0a49-9578-d2a7-fa13dbe82aa5.png)

一覧から使用中のVCN -> サブネットを選択するとセキュリティリストがあります。
![2020-12-13_11h15_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/80e63a77-ea3f-3ade-c947-0a0a2df69311.png)

デフォルトだと`10.0.0.0/16`(同一ネットワーク内のルール)のICMPが中途半端なので、「編集」で一旦全許可(「タイプオプション」が`3`になってるとこを空欄)にします。
これでnode1, node2間で`ping`が応答するようになるはず。

次にKubernetes APIの`6443/TCP`とmetricsの`10250/TCP`とVXLAN(これは正確にはKubernetesでなくCNIのFlannel)の`8472/UDP`も許可設定を追加。
(参考: [Installation Requirements / Networking](https://rancher.com/docs/k3s/latest/en/installation/installation-requirements/#networking) )

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/b68658c5-02d9-eefb-2665-e55d5418ed26.png)

[通常のKubernetesクラスタ](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%AB%E3%83%97%E3%83%AC%E3%83%BC%E3%83%B3%E3%83%8E%E3%83%BC%E3%83%89)であれば、6443と10250以外にも、etcdのための2379-2380などいくつか必要な疎通用のポートがありますが、K3sではコンポーネントの軽量化の関係で、外部向けにListenしているポートが6443と10250の二つのため、これだけで動作します。
(ノード上で`ss -anptl`で確認)

また、デフォルトではK3sはCNIにFlannelを使用する設定になっており、Flannelがノード同士の通信に`8472/UDP`を使用するのでこのポートも許可します。

### Firewalld設定(@ node1)

これでnode2からnode1へ`curl -k https://10.0.0.2:6443`でKubernetesのREST APIに接続できる、、、と思いきや、実はOracle CloudのOracleLinuxインスタンスはFirewalldも有効になってるので、こちらも許可設定を追加します。
また、Flannelによるノード間・Pod間の通信を許可するために、`cni0`というネットワークインタフェースを`trusted`ゾーンに設定します。

```
[opc@node1 ~]$ sudo firewall-cmd --add-port=6443/tcp --permanent
success
[opc@node1 ~]$ sudo firewall-cmd --add-port=10250/tcp --permanent
success
[opc@node1 ~]$ sudo firewall-cmd --add-port=8472/udp --permanent
success
[opc@node1 ~]$ sudo firewall-cmd --zone=trusted --change-interface=cni0
success
[opc@node1 ~]$ sudo firewall-cmd --reload
success
```

これでnode2からnode1のAPIサーバーにアクセスできます。

```
[opc@node2 ~]$ curl -k https://10.0.0.2:6443
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
    
  },
  "status": "Failure",
  "message": "Unauthorized",
  "reason": "Unauthorized",
  "code": 401
}
```

### Firewalld設定(@ node2)

node1と同じ要領で、`10250/tcp`と`8472/udp`を許可し、`cni0`インタフェースを`trusted`ゾーンに設定します。(`cni0`インタフェースはK3sインストール前で存在しなくても設定可能)

```
[opc@node2 ~]$ sudo firewall-cmd --add-port=10250/tcp --permanent
success
[opc@node2 ~]$ sudo firewall-cmd --add-port=8472/udp --permanent
success
[opc@node2 ~]$ sudo firewall-cmd --zone=trusted --change-interface=cni0
success
[opc@node2 ~]$ sudo firewall-cmd --reload
success
```

### tokenの確認(@ node1)

クラスタ参加用のトークンを構築済みnode1上で確認。
これは単にファイルにトークンの内容が書かれているのでそれを見れば良いです。

```
$ sudo cat /var/lib/rancher/k3s/server/node-token
```

### クラスタへの参加(@ node2)

workerノード追加用のオプションを追加して、Control Plane1台目と同様に実行。

```
curl -sfL https://get.k3s.io | K3S_URL=https://10.0.0.2:6443 K3S_TOKEN=${NODE_TOKEN} sh -
```

以下のように、node1と同じように全自動で処理されます。

```
[opc@node2 ~]$ curl -sfL https://get.k3s.io | K3S_URL=https://10.0.0.2:6443 K3S_TOKEN=${NODE_TOKEN} sh -
[INFO]  Finding release for channel stable
[INFO]  Using v1.19.4+k3s1 as release
[INFO]  Downloading hash https://github.com/rancher/k3s/releases/download/v1.19.4+k3s1/sha256sum-amd64.txt
[INFO]  Downloading binary https://github.com/rancher/k3s/releases/download/v1.19.4+k3s1/k3s
[INFO]  Verifying binary download
[INFO]  Installing k3s to /usr/local/bin/k3s
読み込んだプラグイン:langpacks, ulninfo
ol7_UEKR6                                                | 2.8 kB     00:00     
ol7_addons                                               | 2.8 kB     00:00 

:
:

インストール:
  k3s-selinux.noarch 0:0.2-1.el7_8                                              
依存性関連をインストールしました:
  container-selinux.noarch 2:2.119.2-1.911c772.el7_8                            
完了しました!
[INFO]  Creating /usr/local/bin/kubectl symlink to k3s
[INFO]  Creating /usr/local/bin/crictl symlink to k3s
[INFO]  Creating /usr/local/bin/ctr symlink to k3s
[INFO]  Creating killall script /usr/local/bin/k3s-killall.sh
[INFO]  Creating uninstall script /usr/local/bin/k3s-agent-uninstall.sh
[INFO]  env: Creating environment file /etc/systemd/system/k3s-agent.service.env
[INFO]  systemd: Creating service file /etc/systemd/system/k3s-agent.service
[INFO]  systemd: Enabling k3s-agent unit
Created symlink from /etc/systemd/system/multi-user.target.wants/k3s-agent.service to /etc/systemd/system/k3s-agent.service.
[INFO]  systemd: Starting k3s-agent
```

## クラスタ初期状態

### node

ノードの状態を確認すると以下の通り。

```
[opc@node1 ~]$ sudo /usr/local/bin/kubectl get node
NAME    STATUS   ROLES    AGE     VERSION
node1   Ready    master   32m     v1.19.4+k3s1
node2   Ready    <none>   2m39s   v1.19.4+k3s1
```

※ 2021.02.09時点では ver 1.20.2 になっており以下のようになる。

```
[opc@node1 ~]$ sudo /usr/local/bin/kubectl get node
NAME    STATUS   ROLES                  AGE   VERSION
node1   Ready    control-plane,master   16m   v1.20.2+k3s1
node2   Ready    <none>                 11s   v1.20.2+k3s1
```

その他、podやserviceの状態は以下の通り。

### pod

```
[opc@node1 ~]$ sudo /usr/local/bin/kubectl get pod -A -o wide
NAMESPACE     NAME                                     READY   STATUS      RESTARTS   AGE     IP          NODE    NOMINATED NODE   READINESS GATES
kube-system   coredns-66c464876b-4lmg6                 1/1     Running     0          32m     10.42.0.2   node1   <none>           <none>
kube-system   local-path-provisioner-7ff9579c6-twdjb   1/1     Running     10         32m     10.42.0.5   node1   <none>           <none>
kube-system   metrics-server-7b4f8b595-b2j5r           1/1     Running     10         32m     10.42.0.3   node1   <none>           <none>
kube-system   helm-install-traefik-579wg               0/1     Completed   10         32m     10.42.0.4   node1   <none>           <none>
kube-system   svclb-traefik-k7qqx                      2/2     Running     0          4m28s   10.42.0.7   node1   <none>           <none>
kube-system   traefik-5dd496474-5mdcn                  1/1     Running     0          4m29s   10.42.0.6   node1   <none>           <none>
kube-system   svclb-traefik-w9wr4                      2/2     Running     0          3m9s    10.42.1.2   node2   <none>           <none>
```

### storage class
```
[opc@node1 ~]$ sudo /usr/local/bin/kubectl get sc
NAME                   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  33m
```

ローカルストレージを使ったStorageClassも付属しています。

### service

```
[opc@node1 ~]$ sudo /usr/local/bin/kubectl get svc -A
NAMESPACE     NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
default       kubernetes           ClusterIP      10.43.0.1      <none>        443/TCP                      33m
kube-system   kube-dns             ClusterIP      10.43.0.10     <none>        53/UDP,53/TCP,9153/TCP       32m
kube-system   metrics-server       ClusterIP      10.43.14.227   <none>        443/TCP                      32m
kube-system   traefik-prometheus   ClusterIP      10.43.90.59    <none>        9100/TCP                     4m43s
kube-system   traefik              LoadBalancer   10.43.3.194    10.0.0.2      80:31448/TCP,443:31281/TCP   4m43s
```

traefikというtype:LoadBalancer serivceを使うpodもありますが、検索するとK3s標準のIngress Controllerとのこと。
設定されているExternal IPもnode1のプライベートIPアドレスなので、この状態のままだと外部(インターネット経由)からのアクセスはまだ無理かな？

クラスタ外部からのネットワークアクセスは、Oracle CloudのロードバランサーもAlways Free枠で使えたりするので、組み合わせればなんとかなりそうなので、また別途やろうと思います。

# kubectlをローカルから使用

現在の状態だと、`/etc/rancher/k3s/k3s.yaml`に読み取り権限がないので`sudo`が必要だし、そもそもOracle Cloud上のノードにSSHログインする必要があって不便なので、ローカルPC上の`kubectl`から操作できるようにします。

といっても基本的に`/etc/rancher/k3s/k3s.yaml`をローカルにコピー~~するだけ~~して微修正します。

## /etc/rancher/k3s/k3s.yamlのコピー

普通に`scp`など使用します。
読み取り権限与えたりする設定が面倒であれば、`cat`した内容でローカルにファイル作成してもヨシ。

ローカル(cloud-devというホスト名)で、リモートのoci-node1から`k3s.yaml`をコピー。

```
[opc@node1 ~]$ sudo cp /etc/rancher/k3s/k3s.yaml .
[opc@node1 ~]$ sudo chown opc k3s.yaml
[opc@node1 ~]$ exit
ログアウト
Connection to ---.---.---.--- closed.
[zaki@cloud-dev ~]$ scp oci-node1:k3s.yaml work/oci-k3s-kubeconfig.yaml
k3s.yaml                                      100% 2961    15.6KB/s   00:00  
```

## kubeconfigファイル修正

ダウンロードしたkubeconfigファイルは、APIサーバーのアドレスが`127.0.0.1`となっているので、以下の部分をnode1のグローバルIPアドレスに変更します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/a7dc791a-24e9-39c3-cbd1-ed28b045be80.png)

## セキュリティ設定

前述と同じく、今度はTCP/6443をグローバルに許可します。
世界中からのアクセスの許可が不安な場合は、自分のグローバルIPのみピンポイントで許可でも良いです。

![2020-12-13_11h44_52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/048e20da-f6a9-16a9-2642-a84122e9d61f.png)

## insecure設定

標準の`kubectl`は`KUBECONFIG`変数にkubeconfigファイルのパスを指定すればその内容で動作するので、ダウンロード・編集したファイルを指定して実行。

これでAPIサーバーに接続できると思いきや

```
[zaki@cloud-dev ~]$ KUBECONFIG=work/oci-k3s-kubeconfig.yaml kubectl get node
Unable to connect to the server: x509: certificate is valid for 10.0.0.2, 10.43.0.1, 127.0.0.1, not ---.---.---.---
```

特定のアドレス向けにしか証明書が設定されてないらしく、x509エラーとなってしまいます。
仕方ないので、`--insecure-skip-tls-verify`オプションを追加して、エラーを無視して実行します。

```
[zaki@cloud-dev ~]$ KUBECONFIG=work/oci-k3s-kubeconfig.yaml kubectl get node --insecure-skip-tls-verify
NAME    STATUS   ROLES    AGE   VERSION
node1   Ready    master   50m   v1.19.4+k3s1
node2   Ready    <none>   21m   v1.19.4+k3s1
```

動きました。

これだと毎回オプション付けるのが大変なので、kubeconfigファイルに設定してしまいます。
設定か所は以下の通り。

```yaml
- cluster:
    certificate-authority-data:  ...
    server: https://<node1のIPアドレス>:6443
  name: default
  insecure-skip-tls-verify: true # <- ここ

:
:
```

```
[zaki@cloud-dev ~]$ KUBECONFIG=work/oci-k3s-kubeconfig.yaml kubectl get node
NAME    STATUS   ROLES    AGE   VERSION
node1   Ready    master   52m   v1.19.4+k3s1
node2   Ready    <none>   23m   v1.19.4+k3s1
```

## kubeconfigのマージ

このままだとOracle Cloud上のK3sクラスタのみkubeconfigファイルが異なって不便なので、デフォルトの`$HOME/.kube/config`にマージします。

そのままマージすると`default`という名前になって分かりづらいので、クラスタ名・コンテキスト名を予め任意の名前に修正しておきます。
ここでは`oci-k3s`という名前にしています。  
(※ 2021.02.09追記: 当初`user: default`は修正対象外にしてましたが、ほかの設定とのマージ時に同じ名前の`user`が他にもあると設定が消えてしまうため、`user: default`も修正してください)

```yaml
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: ...
    server: https://<node1のIPアドレス>:6443
  name: oci-k3s          # <- ここ
  insecure-skip-tls-verify: true
contexts:
- context:
    cluster: oci-k3s     # <- ここ
    user: oci-k3s        # <- ここ
  name: oci-k3s          # <- ここ
current-context: oci-k3s # <- ここ
kind: Config
preferences: {}
users:
- name: oci-k3s          # <- ここ
:
:
```

設定できたら、`KUBECONFIG`変数に「Oracle Cloudに立てたK3sのkubeconfig」と「標準の`$HOME/.kube/config`」の両方を`:`で接続すれば両方をマージして実行されるので、これと「`kubectl config view`で設定を出力」の合わせ技で、kubeconfigを合体させます。

```
[zaki@cloud-dev ~]$ KUBECONFIG=work/oci-k3s-kubeconfig.yaml:$HOME/.kube/config kubectl config view
apiVersion: v1
clusters:
- cluster:
    server: ...
  name: ...
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: ...
  name: ...
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: ...
  name: ...
- cluster:

:
:
```

特に問題なさそうであれば、この出力は証明書関連が表示されない(`DATA+OMITTED`となっている)ので、更に`--flatten`を付与して再実行。

```
[zaki@cloud-dev ~]$ KUBECONFIG=work/oci-k3s-kubeconfig.yaml:$HOME/.kube/config kubectl config view --flatten > z
[zaki@cloud-dev ~]$ mv z ~/.kube/config
[zaki@cloud-dev ~]$ chmod 600 ~/.kube/config
[zaki@cloud-dev ~]$ kubectl config get-clusters
NAME
kind-multi-1.15
kind-multicluster
kubernetes
oci-k3s
cluster-c2daobwhbsg
[zaki@cloud-dev ~]$ kubectl config get-contexts
CURRENT   NAME                          CLUSTER               AUTHINFO            NAMESPACE
          context-c2daobwhbsg           cluster-c2daobwhbsg   user-c2daobwhbsg
          kind-multi-1.15               kind-multi-1.15       kind-multi-1.15
          kind-multicluster             kind-multicluster     kind-multicluster
          kubernetes-admin@kubernetes   kubernetes            kubernetes-admin
*         oci-k3s                       oci-k3s               default
```

これで他のクラスタと同じように使えるようになりました。

# サンプルアプリ

[Rookのサンプルとして付属しているMySQL(Deployment + PersistentVolume)](https://github.com/rook/rook/blob/master/cluster/examples/kubernetes/mysql.yaml)があるので、これを(少し修正して)デプロイしてみます。

マニフェストは以下の通り。
ストレージサイズを2GBに縮小して、`storageClassName: rook-ceph-block`の指定を削除(`default`使用)しています。

```yaml
apiVersion: v1
kind: Service
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
  clusterIP: None
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
    tier: mysql
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: mysql
    spec:
      containers:
      - image: mysql:5.6
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: changeme
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim
```

デプロイ

```
[zaki@cloud-dev oci-k3s-sample]$ kubectl create ns sample
namespace/sample created
[zaki@cloud-dev oci-k3s-sample]$ kubectl apply -f mysql.yaml -n sample
service/wordpress-mysql created
persistentvolumeclaim/mysql-pv-claim created
deployment.apps/wordpress-mysql created
```

pvがプロビジョニングされてpodもRunningになりました。

```
[zaki@cloud-dev oci-k3s-sample]$ kubectl get pod,svc,pvc -n sample
NAME                                   READY   STATUS    RESTARTS   AGE
pod/wordpress-mysql-6965fc8cc8-tmxc2   1/1     Running   0          49s

NAME                      TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
service/wordpress-mysql   ClusterIP   None         <none>        3306/TCP   50s

NAME                                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/mysql-pv-claim   Bound    pvc-79c2b95a-3548-45e6-8899-9398b799add6   2Gi        RWO            local-path     50s
```

# まとめ

以上で、K3sを使ったOracle CloudのAlways Free枠で使えるコンピュートインスタンス(VM)でKubernetesクラスタを最低限使い始めることができる環境を作って見ました。

ノードのスペックは以下の通りなので、CPUもメモリも潤沢にあるわけではないですが、簡単な動作確認程度には使えますし、何より常時稼働していても費用がかからないのは大きいと思います。

```
[zaki@cloud-dev oci-k3s-sample]$ kubectl describe node | grep -A 7 Allocatable:
Allocatable:
  cpu:                2
  ephemeral-storage:  39129471355
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             705128Ki
  pods:               110
System Info:
--
Allocatable:
  cpu:                2
  ephemeral-storage:  39129471355
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             705128Ki
  pods:               110
System Info:
```

## OCHaCafe

Oracle社主催の勉強会が定期的に開催されています。
参加特典として、クレジットがちょっと多めのOracle Cloudアカウントをもらえたりするのでまだ持ってない人は参加してみてもよいかも。

[Oracle Cloud Hangout Cafe - connpass](https://ochacafe.connpass.com/)

次回は最近話題のコンテナランタイムのお話が予定されています。

[OCHaCafe3 #6 コンテナ・ランタイムの未来 - connpass](https://ochacafe.connpass.com/event/197023/)

## (補足) k0sは使わなかったの？

似たような[軽量ディストリビューションでk0sとういもの](https://k0sproject.io/)があります。
以前ローカル環境でも試してみたことがあります。

[[Kubernetes] 軽量ディストリビューション k0s をローカルで動かしてみた - zaki work log](https://zaki-hmkc.hatenablog.com/entry/2020/11/17/091559)

今回使わなかったのは、(確認当時は)k0s自体にはストレージのプロビジョニングは機能として内蔵しておらず、ノードにディスクを積んでRookなり使ってプロビジョニングする必要が(少なくとも試した限りでは)ありそうでした。
Oracle CloudのAlways Free枠では、ストレージは50Gが2台までという内容になっており、この2台のストレージはノードのOS用で使い切ってしまうため、k0s+Rookの場合で必要になる「クラスタ用途でのストレージ」が用意できそうになかったため、今回は選択肢から外しました。
(あと単に、K3sを使ったことがなかったので試してみたかったというのが本音ｗ)

クラスタからストレージを使わないのであればk0sを試してみるのももちろんアリです。