Kubespray でオフライン・プロキシ環境下に Kubernetes を構築

## 概要
ベアメタルのリモートサーバに Kubernetes を導入する際、対象のホストが

- Ansible の要件 (SSH 接続可能 + Python3 導入済み) は満たしている
- HTTP/HTTPS で外部に接続できない

のような場合に kubespray を適用する方法を説明する。
手順は公式ドキュメント [kubespray/docs/offline-environment.md](https://github.com/kubernetes-sigs/kubespray/tree/master/contrib/offline#offline-deployment) に従う。

大まかな手順は次の通り：
1. Client 側で、kubespray の依存を配布するための HTTP server / container registry を起動する
1. SSH リモートフォワーディングで Server 側から client の HTTP server / container registry にアクセス可能にする
1. kubespray を実行する

![kubespray-offline.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261727/2fd74e4f-22ee-589e-42a2-0e0340b6c8d2.png)

サンプルコード：

https://github.com/sawa2d2/k8s-on-kvm/tree/main/kubernetes/offline

## 前提
### OS
- Continer engine (`docker`, `podman`, `nerdctl`, etc.)
- Client: MacOS (Ventura 13.5)
- Server: (RHEL 8.8)

:::note info
コンテナエンジン関する補足：
本記事では　MacOS かつ コンテナエンジンに Podman を使うことを前提にしていますが、 `docker` や `nerdctl` を使う場合は適宜、読み替えてください。

本記事では podman machine を次で作成しています：
```
$ podman machine init \
    --volume /Users:/mnt/Users \
    --volume /Volumes:/mnt/Volumes \
    --volume /private:/mnt/private \
    --volume /tmp:/mnt/tmp \
    --volume /var/folders:/mnt/var/folders
```
:::

## 手順
:::note warn
本記事において、オフラインの yum repository を構成する設定に関しては執筆中です。
しかしオフライン HTTP server については動作するため、「HTTP/HTTPS はうまくつながらないが yum repo は利用できる」というケースで有効です。

（オフライン yum repo の構築方法も、いずれ書きます😔）
:::


### サーバの事前準備
Kubespray 実行時に求められる `conntrack`, `socat`, `ipvsadm` を事前に入れる。
```
# On remote
$ dnf update -y
$ dnf install -y conntrack-tools socat ipvsadm
```

firewalld を停止・無効化する
```
$ systemctl stop firewalld
$ systemctl disable firewalld
```

### Kubespray の準備
github から kubespray をクローンする。
```
# On client

$ git clone https://github.com/kubernetes-sigs/kubespray.git
$ cd kubespray/contrib/offline
```

### HTTPサーバの構築

クライアント側で Kubespray の `./contrib/offline/generate_list.sh` を実行すると、必要な files / images のリストが生成される。
注意点として、このスクリプトは、実行するマシンの OS に合わせたファイルをダウンロードするため、本記事のように Mac 上で実行すると、`linux` ではなく `darwin` の依存ファイルが取得される。これを防ぐため、[`generate_list.sh` の最終行](https://github.com/kubernetes-sigs/kubespray/blob/release-2.22/contrib/offline/generate_list.sh#L33)：ansible-playbook コマンドの実行時に、 `-e "ansible_system=linux"` option を与える。

```:(Before) generate_list.sh
(cd ${REPO_ROOT_DIR} && ansible-playbook $* generate_list.yml && /bin/rm generate_list.yml) || exit 1
```

```:(After) generate_list.sh
(cd ${REPO_ROOT_DIR} && ansible-playbook -e "ansible_system=linux" $* generate_list.yml && /bin/rm generate_list.yml) || exit 1
```

修正後の `generate_list.sh`を実行して、リスト が `temp/` 内に生成されたことを確認する。
```
$ ./generate_list.sh
...
$ ls temp/
files.list files.list.template images.list images.list.template 
```

次に、`./contrib/offline/manage-offline-files.sh` を実行すると、必要な files をダウンロードされ、それらの入った HTTP サーバ (nginx コンテナ) が自動で立ち上がる。ただし、本記事のように podman ユーザで client が Mac の場合、本記事のように[ローカルパスを `/mnt` にマウントして podman machine init](https://qiita.com/drafts/85b01696a229c23a7f61/edit#%E5%89%8D%E6%8F%90%E6%9D%A1%E4%BB%B6)  した場合、マウントするパスの先頭に `/mnt/` をつける必要がある。この点は [`manage-offline-files.sh` の最後のブロック](https://github.com/kubernetes-sigs/kubespray/blob/release-2.22/contrib/offline/manage-offline-files.sh#L41-L42) では考慮されていなため、次のように修正する。

```:(Before) manage-offline-files.sh
        --volume "${OFFLINE_FILES_DIR}:/usr/share/nginx/html/download" \
        --volume "$(pwd)"/nginx.conf:/etc/nginx/nginx.conf \
```

```:(After) manage-offline-files.sh
        --volume /mnt/"${OFFLINE_FILES_DIR}:/usr/share/nginx/html/download" \
        --volume /mnt/"$(pwd)"/nginx.conf:/etc/nginx/nginx.conf \
```

修正後の `generate_list.sh`を実行し、`nginx` コンテナが起動したことを確認する：

```
# 既存の `nginx` という名のコンテナを削除
$ podman kill nginx; podman rm nginx
$ ./manage-offline-files.sh
...

$ curl localhost:8080
<!DOCTYPE http>
<http>
<head>
<title>Welcome to nginx!</title>
...
```

### コンテナレジストリの構築
(執筆中..:bow:)
`./contrib/offline/manage_offline_images.sh` を実行すると、必要な images がダウンロードされ、それら全てが入ったコンテナレジストリが起動する。


### リモートフォワーディング
SSH リモートフォワーディングで、リモートサーバから local に立ち上げたHTTPサーバおよびコンテナレジストリにアクセスできるようにする (`:8080` が hTTPサーバ、`:5000` がコンテナレジストリにフォワードされる)。
```
# On client
$ ssh -R 8080:localhost:8080 -R 5000:localhost:5000 user@remote_host

# On remote
$ curl localhost:8080
$ curl -X GET http://localhost:5000/v2/_catalogs
```

ここで立ち上げた SSH セッションは維持しておくこと。


### inventory の修正
(執筆中..:bow:)

[inventory の `groups_vars/all/offline.yml`](https://github.com/kubernetes-sigs/kubespray/blob/release-2.22/inventory/sample/group_vars/all/offline.yml) を修正する。
`files_repo` を `http://localhost:8080/download` にする。

```:groups_vars/all/offline.yml
files_repo: "http://localhost:8080/download"
```

かつ、同 yamlファイル内で `{{ files_repo }}` を参照している部分のコメントアウトをすべて外す。

### Kubespray の実行
リモートホストに対して kubespray を実行する。


## 補足
リモートサーバへ接続時に踏み台サーバを経由する (プロキシジャンプする) 場合は、`inventory/mycluster/host.yml` の `ansible_ssh_common_args` に次のような SSH コマンドオプションを追加する。
```
all:
  hosts:
    host_hoge:
      ansible_ssh_common_args: '-J <user>@<proxy_host>:<port>'
```


---

## 関連記事
https://qiita.com/sawa2d2/items/c592dcbd958f69441068