k3sでJupyterLabを起動してみた話

## モチベーション

これをやり始めたのは以下の理由によります。
- 使えもしないのに、使う目的も特にないのに、なぜかkubernetesが好きだから。（なぜなのか）

## 今回やったこと
- k3sでクラスタ構築し、JupyterLabをデプロイしてネットワーク内から利用できるようにした。

## 参考にした記事
- 本稿は、主にこちらの記事を参考にしています。
https://qiita.com/otsukousan/items/5d7a786aa7a2e7335368
- 認証情報の扱いなどはこちらを参考にしています。
https://hassiweb.gitlab.io/memo/docs/memo/k3s/k3s-installation/
- コマンド補完設定はこちらを参考にしています。
https://unicorn.limited/jp/rd/kubernetes/20210527-shorthand.html

## 構成
ESXi基盤に、マスター1台、ノード2台を用意。それぞれUbuntu22.04を新規インストールした。（ストレージ16GB、メモリ4GB、CPUコア8個）
k3smaster
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529114/60491deb-5032-5ec7-e604-38742f676805.png)

## 手順の概要

1. 下準備
1. マスターサーバにコントロールプレーン設定をする。
1. ↑にノードを１台ずつ追加する。
1. LoadBalancerをデプロイする。
1. JupyterLabをデプロイする。

## 詳細な手順

### 1. 下準備

#### クライアントPCにkubectlをインストールする。
以下の手順はmacOSでHomebrewによるセットアップ手順になります。

```shell
brew install kubectl
```

#### クライアントPCでkubectlコマンドのタブ補完を有効にする。

#　以下を`~/.zshrc`に追記してリロード（`. ~/.zshrc`）。（macOSの場合）
```
source <(kubectl completion zsh)
```

#### クライアントの公開キーを対象サーバにコピーする。

クラスタを構築するまでに何度かサーバにログインするので、サーバログイン時にパスワードを入力しなくても済むようにしています。

```shell
ssh-copy-id 192.168.150.181 #これをそれぞれのサーバに実施する。
```

#### 各サーバの/etc/hostsを編集

3台それぞれに同じ作業を実施します。

```/etc/hosts
192.168.150.181 k3smaster
192.168.150.182 k3snode01
192.168.150.183 k3snode02
```

### 2. マスターサーバにコントロールプレーン設定

マスターサーバのログインし、以下のコマンドを実行します。

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

正常にインストール完了したら`/var/lib/rancher/k3s/server/token`の値をコピーしておきます。（次のノード追加で使います）

マスターサーバからはいったんログアウトします。

### 3. ノード追加

それぞれのノードで以下のコマンドを実行します。
`K3S_TOKEN`のところに上でコピーした文字列が入ります。

```shell
curl -sfL https://get.k3s.io |\
K3S_TOKEN="Kxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx::server:xxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
K3S_URL=https://k3smaster:6443 sh -
```

### 動作確認
2台のノード追加が終わったら、いったん動作確認してみます。
マスターサーバにログインし、
```shell
sudo kubectl get node
```
以下のようにノードのステータスがReadyになっていれば成功です。
```
NAME        STATUS   ROLES                  AGE   VERSION
k3snode01   Ready    <none>                 33h   v1.27.7+k3s2
k3snode02   Ready    <none>                 33h   v1.27.7+k3s2
k3smaster   Ready    control-plane,master   33h   v1.27.7+k3s2
```

### クレデンシャル情報のコピー
マスターノードで、以下のパスにあるファイルの中身をコピーします。

```/etc/rancher/k3s/k3s.yaml
sudo cat /etc/rancher/k3s/k3s.yaml

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    server: https://127.0.0.1:6443
  name: default
contexts:
- context:
    cluster: default
    user: default
  name: default
current-context: default
kind: Config
preferences: {}
users:
- name: default
  user:
    client-certificate-data: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    client-key-data: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- name: username
  user:
    password: password
    username: username
```


これはk3sクラスタへアクセスするためのクレデンシャル情報なので、本番では扱いを考えなければならないですが、ここではいったん置いておきます。

マスターサーバからログアウトし、クライアントPCに`~/.kube/config`を新規作成して、上記クレデンシャル情報をペーストします。

ここで、IPアドレスを127.0.0.1からマスターサーバのIPアドレスに書き換えておきます。

```diff
-    server: https://127.0.0.1:6443
+    server: https://192.168.150.181:6443
```

ここで、クライアントPCで以下のコマンドを実行します。

```shell
kubectl get node
```

以下のように表示されれば成功です。

```結果
NAME        STATUS   ROLES                  AGE   VERSION
k3snode01   Ready    <none>                 34h   v1.27.7+k3s2
k3snode02   Ready    <none>                 34h   v1.27.7+k3s2
k3smaster   Ready    control-plane,master   34h   v1.27.7+k3s2
```

ここからはクライアントPCのkubecltでクラスタを管理していくことになります。

### 4. LoadBalancerのデプロイ

k3sで、単純にLoadBalancerを指定してもうまくいかなかったので、手動でデプロイすることにします。（やり方はChatGPTに聞きました）

##### metallbをデプロイ

まずは以下を実行します。

```
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/metallb.yaml
```

次に`metallb-config.yml`を以下のように作成します。

```metallb-config.yml
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 192.168.150.170-192.168.150.179  # 使いたいIPアドレスの範囲
```

以下のコマンドを実行してデプロイします。

```shell
kubectl apply -f metallb-config.yml
```

これでLoadBalancerの準備ができたはずです。以下のコマンドを実行してみます。

```
kubectl get service
```

以下のようにTYPE LoadBalancerの　EXTERNAL−IPが指定したIPアドレスになっていれば成功です。

```
NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)        AGE
kubernetes           ClusterIP      10.43.0.1      <none>            443/TCP        34h
jupyterlab-service   LoadBalancer   10.43.74.148   192.168.150.171   80:32107/TCP   141m
```

### 5. JupyterLabのデプロイ

以下の2ファイルを作成します。

```jupyterlab-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jupyterlab-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jupyterlab
  template:
    metadata:
      labels:
        app: jupyterlab
    spec:
      containers:
      - name: jupyterlab
        image: jupyter/base-notebook:latest  # JupyterLabが含まれたイメージを使用するか、カスタムイメージを指定する
        ports:
        - containerPort: 8888
          name: jupyterlab
```

```jupyterlab-service.yml
apiVersion: v1
kind: Service
metadata:
  name: jupyterlab-service
spec:
  selector:
    app: jupyterlab
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8888
  type: LoadBalancer
```

上記2ファイルをapplyします。

```shell
kubectl apply -f jupyterlab-deployment.yml -f jupyterlab-service.yml
```

これでJupyterLabは起動しているはずです。確認してみましょう。

```
kubectl get pod
```

以下のように出力されればjupyterlabのpodが起動していることが分かります。
``` 結果
NAME                                    READY   STATUS    RESTARTS   AGE
jupyterlab-deployment-xxxxxxxx-xxxxx   1/1     Running   0          12h
```

ここで、NAME欄をコピーしておいて、以下のコマンドを実行します。
```
kubectl logs jupyterlab-deployment-xxxxxxxx-xxxxx
```

すると、以下のような出力がされているはずです。

```
[C 2023-11-21 11:46:00.954 ServerApp] 
    
    To access the server, open this file in a browser:
        file:///home/jovyan/.local/share/jupyter/runtime/jpserver-7-open.html
    Or copy and paste one of these URLs:
        http://jupyterlab-deployment-bdf776574-mppdj:8888/lab?token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
        http://127.0.0.1:8888/lab?token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

このtokenをコピーします。

これでやっとJupyterLabを使う準備ができました。

上の方でLoadBalancerのEXPTERNAL-IPが出力されていたはずです。そこにブラウザでアクセスしましょう。

以下のような画面が出てきているはずです。
`Password or token`のところに、先程コピーしたTOKENを入れて［Log in］をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529114/2ba29f52-fa02-14a6-2c44-053a3e3be87c.png)

お疲れ様でした。