KubernetesでDocker Registryを構築する。


前提

Dockerでコンテナを構築する場合、コンテナイメージを持っていない場合自動的にDocker Hub等からダウンロードする。
これはKubernetesでも同様で、動くワーカノード上でコンテナイメージを持っていなければ自動でダウンロードする。(デフォルト定義の場合)
なので、クローズ環境でKubernetesを動かしたい場合は事前にすべてのワーカノード上に動作させるコンテナイメージを事前にロードしておく必要がある。

docker load -i xxx.tar 

ワーカノードが1台とかそんな話であれば問題はないけど、Kubernetes構築する以上○台インストールはあり得る上、それぞれに○個のコンテナイメージロードとか面倒です。

というわけで、KubernetesにDocker Hub的なもの(正式名称:Registry)を立ててそこからアクセスさせようというのが今回の目的です。

環境

Ubuntu 18.04.4 LTS
Docker version 19.03.6, build 369ce74a3c
Kubernetes v1.18(Kubeadm v1.8で3台ノード構築済み)
Rook+Ceph v1.4 (永続ボリューム用)
Rook+Cephの構築については追々書きます。

Docker Registryについて

細かい話は公式に記載しているので、とりあえず置いておいて、DockerでDocker Registryを構築するだけなら次のコマンドで問題ありません。

docker run -d -p 5000:5000 -v /home/user0/registry:/var/lib/registry registry

これでdocker pull localhost:5000/httpd:latest のように使用することができる。
なお、コンテナにタグlocalhost:5000/xxxをつけておく必要がある。

しかし、これはlocalhostで接続する場合の話。
ドメイン(またはIPアドレス)で指定する場合、docker pull 192.168.200.5:5000/httpd:latestのように指定すれば解決かと思いきや、必ずHTTPS通信をするのでregistryコンテナに証明書を設定し起動する必要がある。
もし、registryコンテナ側に証明書の設定がないと次のようにエラーとなる。

マニュアルに証明書の設定方法が記載されているのでそれを参照に設定したところで、適当な証明書では通信してくれない。

めんどくさいことに、自己署名の証明書を入れてもダメ。

ちゃんとした証明書があれば次の章は解決しますが、試験環境など正規な証明書が準備できない場合は次の通り証明書を作成します。

Docker用の証明書を作る

 解決法としてはルート証明書をクライアント(Dockerのホスト側)にインストールし、ルート証明書で署名されたサーバ証明書をサーバ(registryコンテナ側)にインストールする方法をとります。
 ここではドメインの証明書なので、hostsファイルか(ローカルの)DNSでドメイン名が解決できるようにしておきます。

ルート証明書を作る

 ルート証明書の作り方はよく出てるのでコマンドだけ。なお、チャレンジパスワードは空欄でないとエラーになります。

openssl genrsa 2048 > ca.key
openssl req -new -key ca.key  > ca.csr
openssl x509 -days 365 -req -signkey ca.key < ca.csr > ca.crt

サーバ証明書を作る

 サーバ証明書を作る前に、opensslの作業ファイル等を作ります。

mkdir demoCA
cd demoCA
mkdir private crl certs newcerts 
echo "01" > serial
touch index.txt
cd ../

 (21.11.28追記)サーバ証明書にSANを追加するため、次のテキストファイルを作ります。

san.txt
subjectAltName = DNS:*.example.local, IP:192.168.0.1

 それで、サーバ証明書を作成しルート証明書で署名します。 
 この時、サーバ証明書とルート証明書の項目に不一致があるとエラーが発生する項目があります。

openssl genrsa 2048 > server.key
openssl req -new -key server.key  > server.csr
openssl ca -days 365 -cert ca.crt -keyfile ca.key -in server.csr -extfile san.txt > server.crt

ルート証明書をDockerのホストに追加する

 この項目は、OSによって違うのでUbuntuの場合のみ記載します。
 ルート証明書を指定のディレクトリにコピーし、/etc/ca-certificates.confファイルに相対パスで追記します。(この場合はca.crtと追記)
 最後に再読み込みして完了です。

sudo cp ca.crt /usr/share/ca-certificates
sudo nano /etc/ca-certificates.conf 
sudo update-ca-certificates

KubernetesでDocker Registryを構築

 Kubernetesで構築する場合の説明。

事前準備

 ・すべてのワーカノードのDockerにコンテナイメージ(registry)を入れておく
 ・前章の通りサーバ証明書とルート証明書を作る
 ・ルート証明書を全てのワーカノードに反映しておく

永続ボリュームの作成

 Registryのコンテナイメージの保存先(/var/lib/registry/)を作成します。
 Webサーバと同じ扱いで特にコンテナごとにPVを分ける必要性もないため、今回の永続ボリュームはrook+cephのファイルストレージで作成します。なお、NFSのPVでも支障はないと思います。

pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvcregistry
  labels:
    pvc: registry
spec:
  storageClassName: rook-ceph-fs
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 5Gi

 保存したら、kubernetesに読み込ませます。
kubectl apply -f pvc.yaml

サービスの作成

 この後Pod(Deployment)を作ったところで、IPアドレスを固定させないとめんどくさいことになるのでサービスで半固定のIPアドレスを発行させます。
 サービスでは、LoadBalancer、NodePort、ClusterIPの3種類が選べると思います。Kubernetesクラスタ外部にregistryを公開するのであればLoadBalancer型でいいと思いますが、今回はクラスタ内部だけに使えるregistryとして公開します。(=ClusterIPとして構築)

services.yaml
apiVersion: v1
kind: Service
metadata:
  name: srvregistry
spec:
  ports:
  - name: http
    port: 443
    protocol: TCP
    targetPort: 443
  selector:
    app: registry
  type: ClusterIP

 保存したら、kubernetesに読み込ませます。
kubectl apply -f services.yaml
 
 次のコマンドでClusterIPを確認します。
kubectl get services

 Kubernetes内の全ノードでドメインがこのIPアドレスへ解決できるようにDNSまたはhostsファイルを設定します。

証明書の保存

 kubernetesのsecretにサーバ証明書を保存します。
 サーバ証明書ファイルが保存されているディレクトリで次のコマンドを実行します。
 kubectl create secret generic scregistry --from-file=server.crt --from-file=server.key
 

Deploymentの作成

 最後にregistryのコンテナを作成します。
 ポート番号が443になるように設定変更を行っています。

deplyment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: depregistry
  labels:
    dep: registry
spec:
  replicas: 1
  selector:
    matchLabels:
      app: registry
  template:
    metadata:
      labels:
        app: registry
    spec:
      containers:
      - name: registry
        image: registry
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 443
        volumeMounts: 
        - name: vol
          mountPath: /var/lib/registry 
        - name: conf
          mountPath: /certs/
        env:
        - name: REGISTRY_HTTP_TLS_CERTIFICATE
          value: /certs/server.crt 
        - name: REGISTRY_HTTP_TLS_KEY
          value: /certs/server.key
        - name: REGISTRY_HTTP_ADDR
          value: 0.0.0.0:443
      volumes: 
      - name: vol
        persistentVolumeClaim: 
          claimName: pvcregistry
      - name: conf
        secret:
          secretName: scregistry

 保存したら、kubernetesに読み込ませます。
kubectl apply -f deployment.yaml

Registryを使ってみる

Dockerで使用する場合

 Dockerでpushやpullを使うときは、docker pull (ドメイン名):(ポート番号)/(コンテナ名)のように定義します。(ただしポート番号が443の場合はポート番号を省略できます)
 なお、pushをする場合は事前に(ドメイン名):(ポート番号)/(コンテナ名)というタグを定義する必要があります。

docker tag ubuntu www.example.co.jp/ubuntu
docker push www.example.co.jp/ubuntu
docker pull www.example.co.jp/ubuntu

Kubernetesで使用する場合

 Kubernetesで使用する場合は、yamlファイル内のimage: を(ドメイン名):(ポート番号)/(コンテナ名)と定義します。
 前章のようにregistryを作成した場合(ドメイン名www.example.co.jp、ポート番号443)にPodを作る場合は次のようになります。

pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: testpod
spec:
  containers:
  - name: test
    image: www.example.co.jp/httpd:latest
    ports:
    - containerPort: 80

なお、registryを立ち上げた直後はコンテナイメージがないため、Deployment等でイメージ展開する前に前章の通りコマンドを実行し、registry上にイメージをpushしておく。

(参考)そもそもKubernetesのDeploymentとして構築しない場合

 Docker本体でHTTPS通信対応registryを起動させる場合は、以下のコマンドで実行する。または、以下のコマンドを参照し、docker-compose経由で起動する。

docker run -d  \
-p 443:443 \
-v /home/user0/registry:/var/lib/registry  \
-v /home/user0/certs:/certs/ \
-e REGISTRY_HTTP_TLS_CERTIFICATE:/certs/server.crt  \
-e REGISTRY_HTTP_TLS_KEY:/certs/server.key \
-e REGISTRY_HTTP_ADDR:0.0.0.0:443 \
registry

参考文献

レジストリ・サーバのデプロイ — Docker-docs-ja 17.06 ドキュメント
Sanwa Systems Tech Blog | OpenSSLコマンドでオレオレ証明書を作り、ルート認証局としてサーバー証明書を発行する