自分で作ったコンテナレジストリにdocker pushし、KubernetesのPodをデプロイする

# はじめに

Dockerのコンテナイメージを、自分で作ったコンテナレジストリにpushしてDockerやKubernetesからpullして運用する方法について本記事にまとめます。Docker Hubやクラウドのコンテナレジストリを使う手もあると思いますが、多くの場合はそれなりの費用が掛かります。なので既存のコンテナレジストリサービスを使わず、自分でコンテナレジストリを作ってコストを節約したい方などの参考になれば幸いです。

本記事に記載する内容は以下です。

* Dockerを使ってコンテナレジストリを作成する方法
* 作成したコンテナレジストリにDockerからpushおよびpullする方法
* KubernetesのPodに自作コンテナレジストリのイメージをpullしてデプロイする方法

# 前提条件

* OS : Linux
    * 動作確認したのはUbuntuとDebian
* インストールが必要なツール
    * Docker
    * Kubernetesクラスター
        * 筆者が作成したものですが、[k8s-setup](https://github.com/showchan33/k8s-setup/blob/main/README.ja.md)等を使って構築可能です
        * 尚、K8sクラスターが使うコンテナランタイムは**containerdであることを想定**しています

# コンテナレジストリの作成

コンテナイメージを格納するためのコンテナレジストリを作成します。

```
docker run -d --name docker-registry -p 5000:5000 registry
```

コンテナが起動しているのを確認します。

```
$ docker ps -f "name=registry"
CONTAINER ID   IMAGE        COMMAND                   CREATED         STATUS         PORTS                                       NAMES
cadcd49f9a3a   registry     "/entrypoint.sh /etc…"   7 minutes ago   Up 7 minutes   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   docker-registry
```
また、HTTPでコンテナレジストリのサービスが提供されているのを確認します。
※``[hostname]``の部分は実際には、コンテナレジストリを起動したマシンのホスト名やIPアドレスを記入します。

```
$ curl -k http://[hostname]:5000/v2/_catalog
{"repositories":[]}
```

# コンテナレジストリにpushできる状態にする

## docker pushが"できない"ことを確認

まずは「作成したコンテナレジストリにpushできない」ことを、busyboxのコンテナイメージで確認してみます。

以下のコマンドで、Docker Hubからbusyboxのコンテナイメージを取得後、自分で作ったコンテナレジストリにpushするために別名をつけます。
```
docker pull busybox:1.36.1
docker tag busybox:1.36.1 [hostname]:5000/busybox:1.36.1
```

このままコンテナレジストリにpushしようとすると、敢えなく失敗します。

```
$ docker push [hostname]:5000/busybox:1.36.1
The push refers to repository [[hostname]:5000/busybox]
Get "https://[hostname]:5000/v2/": http: server gave HTTP response to HTTPS client
```

ログより、DockerはレジストリのデフォルトのプロトコルはHTTPSを想定しているようです。今回pushしたいレジストリはHTTPなので、HTTPのレジストリにpushできるようにDockerの設定を変更する必要があります。


## docker pushとpullを可能にするための設定

``/etc/docker/daemon.json``を編集して、``insecure-registries``にコンテナレジストリのホスト名とポートを追記します。

```json:/etc/docker/daemon.jsonの一部
{
  "insecure-registries": ["[hostname]:5000"]
}
```

Dockerサービスを再起動します。
```
sudo systemctl restart docker
```

サービス再起動によりコンテナレジストリも止まってしまった場合は、以下のコマンドでコンテナレジストリを再起動させる必要があります。
```
docker start docker-registry
```

## docker pushできる事を確認

再びコンテナイメージのpushを試みると、問題なくpushできるはずです！

```
$ docker push [hostname]:5000/busybox:1.36.1
The push refers to repository [[hostname]:5000/busybox]
95c4a60383f7: Pushed
1.36.1: digest: sha256:db16cd196b8a37ba5f08414e6f6e71003d76665a5eac160cb75ad3759d8b3e29 size: 527
```

コンテナレジストリのHTTPサービスにアクセスして、busyboxのイメージが追加されていることも確認できます。
```
$ curl -k http://[hostname]:5000/v2/_catalog
{"repositories":["busybox"]}
```

一旦ローカルのコンテナイメージを消して、pushしたコンテナイメージがpullできることも確認してみましょう。

```
docker rmi [hostname]:5000/busybox:1.36.1
docker rmi busybox:1.36.1
```
問題なくpullできました！
```
$ docker pull [hostname]:5000/busybox:1.36.1
1.36.1: Pulling from busybox
3a2e9cc4b126: Pull complete
Digest: sha256:db16cd196b8a37ba5f08414e6f6e71003d76665a5eac160cb75ad3759d8b3e29
Status: Downloaded newer image for [hostname]:5000/busybox:1.36.1
[hostname]:5000/busybox:1.36.1
```

# KubernetesのPodをデプロイしてみる

続いて、自作のコンテナレジストリにpushしたbusyboxのコンテナイメージを使って、KubernetesのPodを立ち上げるための設定を行なっていきます。

## コンテナイメージがpullできないことの確認

まずは何も設定せずにbusyboxのコンテナイメージでPodをデプロイしてみましょう。

```
kubectl run busybox-tmp -n default \
--image=[hostname]:5000/busybox:1.36.1 \
--command -- tail -f /dev/null
```

現状だと上手くpullできません。
```
$ kubectl get pod -n default
NAME          READY   STATUS         RESTARTS   AGE
busybox-tmp   0/1     ErrImagePull   0          8s
```

詳細を見ると、「``https://[hostname]:5000``にアクセスできない」とメッセージが出ていることを確認できます。
```
$ kubectl describe pod -n default busybox-tmp | grep -A 32 ^Event
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  85s                default-scheduler  Successfully assigned default/busybox-tmp to livaz
  Normal   Pulling    45s (x3 over 84s)  kubelet            Pulling image "[hostname]:5000/busybox:1.36.1"
  Warning  Failed     45s (x3 over 84s)  kubelet            Failed to pull image "[hostname]:5000/busybox:1.36.1": failed to pull and unpack image "[hostname]:5000/busybox:1.36.1": failed to resolve reference "[hostname]:5000/busybox:1.36.1": failed to do request: Head "https://[hostname]:5000/v2/busybox/manifests/1.36.1": http: server gave HTTP response to HTTPS client
  Warning  Failed     45s (x3 over 84s)  kubelet            Error: ErrImagePull
  Normal   BackOff    15s (x4 over 83s)  kubelet            Back-off pulling image "[hostname]:5000/busybox:1.36.1"
  Warning  Failed     15s (x4 over 83s)  kubelet            Error: ImagePullBackOff
```

## pull可能にするための設定

上記の問題を解消して、自作コンテナレジストリのイメージをKubernetesで使えるための設定をしていきます。

:::note warn
本記事では、K8sのコンテナランタイムにcontainerdを使っていることが前提となります。dockerdやcri-o等の他のコンテナランタイムを使っている場合は、各コンテナランタイムに応じた設定を行なってください。
:::

``/etc/containerd/config.toml`` に以下を追記します。

```toml:/etc/containerd/config.tomlの一部
[plugins."io.containerd.grpc.v1.cri".registry]
   config_path = "/etc/containerd/certs.d"
```
   
次のように、``[hostname]:5000``という名前のディレクトリを作成します。

```
sudo mkdir -p /etc/containerd/certs.d/[hostname]:5000
```
作成したディレクトリ内に、``hosts.toml``というファイルを作成します。
```
sudo touch /etc/containerd/certs.d/[hostname]:5000/hosts.toml
```

``hosts.toml``は次のような内容になるように編集します。

```toml:hosts.toml
server = "http://[hostname]:5000"

[host."http://[hostname]:5000"]
  capabilities = ["pull", "resolve"]
  skip_verify = true
```
  
設定を反映させるために、containerdを再起動します。
```
sudo systemctl restart containerd
```

一旦Podを消して、再び同じPodを作ります。
（本当は消さなくてもいいかもしれないですが、なるべく早くpullできることを確認するためにそうしました。）

```
kubectl delete po -n default busybox-tmp
kubectl run busybox-tmp -n default \
--image=[hostname]:5000/busybox:1.36.1 \
--command -- tail -f /dev/null
```

今度は問題なくPodを起動できました！

```
$ kubectl get pod -n default
NAME          READY   STATUS    RESTARTS   AGE
busybox-tmp   1/1     Running   0          14s
```

```
$ kubectl describe pod -n default busybox-tmp | grep -A 32 ^Event
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  34s   default-scheduler  Successfully assigned default/busybox-tmp to livaz
  Normal  Pulling    33s   kubelet            Pulling image "[hostname]:5000/busybox:1.36.1"
  Normal  Pulled     32s   kubelet            Successfully pulled image "[hostname]:5000/busybox:1.36.1" in 873ms (873ms including waiting)
  Normal  Created    32s   kubelet            Created container busybox-tmp
  Normal  Started    32s   kubelet            Started container busybox-tmp
```

# おわりに

今回書いた内容は、約一年半前に筆者が書いた以下の記事にも実は出てきます。

https://qiita.com/showchan33/items/831d04433999cb961663

この記事は「KubernetesのOperator作成を自分で作ったコンテナレジストリで実現する」ために書いたものですが、当時一番やりたかったのはKubernetesのOperator作成で、自作レジストリに関する設定はそれを実現するために必要になってしまったから仕方なくやったという位置づけでした。
そして最近になって自作レジストリ周りの設定をやり直す機会があったのですが、過去に上の記事に書いてまとめたのをすっかり忘れている自分がいました。「確かどこかにその記録をしていたはず」と思い、自分のメモやQiitaの記事を時間をかけて探してもなかなか見つからず。。。自分のメモの中に「KubernetesのOperator作成の記事に前に書いた」みたいな一言が書かれていたおかげて、ようやく設定ができたという顛末でした。
「自分が書いたものでも見つからないんだから、他の人が見つけるのはなおさら困難だろうなあ」と思い、自作コンテナレジストリの部分だけ切り出して改めて本記事にまとめてみました。お役に立てれば幸いです。

# 参考にしたサイト

* Dockerで自作レジストリにpush可能にするための設定
    * https://matsuand.github.io/docs.docker.jp.onthefly/registry/insecure/
* containerdで自作レジストリからpull可能にするための設定
    * https://github.com/containerd/containerd/blob/main/docs/hosts.md#bypass-tls-verification-example