学習用Kubernetes環境を作る3 Kubernetes上にKubernetes用プライベートDockerレジストリHarbor環境を作る/httpなDockerレジストリをDocker・containerdで使う

## はじめに

**注意:今回作る環境は,初心者が学習用に作成したものです.**
**決して本番環境等,外部に公開するような重要な環境ではそのままの手順通りで行わないで下さい.**
**また,insecureな設定やベストプラクティスから外れた運用をしている箇所があります.**

学習用Kubernetes環境を作るの第3回です.

Part 1 [学習用Kubernetes環境を作る1 Ubuntu 20.04 LTS+kubeadm+containerd+CalicoなKubernetes環境をHyper-V上にTailscaleの固定IPで構築](https://qiita.com/Hyper-W/items/3b1dd5523eed42786fe8)

Part 2 [学習用Kubernetes環境を作る2 動的PVが使えない環境でKubernetes管理WebUIのPortainer環境を作る/データ永続化/NodePortについて](https://qiita.com/Hyper-W/items/e3b81da2bfc1a82c3c3f)

Part 3 [学習用Kubernetes環境を作る3 Kubernetes上にKubernetes用プライベートDockerレジストリHarbor環境を作る/httpなDockerレジストリをDocker・containerdで使う](https://qiita.com/Hyper-W/items/5b36a03eae172e53109f)

KubernetesでDockerコンテナを利用出来るようにローカルにプライベートDockerレジストリーを作成します.

Kubernetesの環境は,[第1回](https://qiita.com/Hyper-W/items/3b1dd5523eed42786fe8),[第2回](https://qiita.com/Hyper-W/items/e3b81da2bfc1a82c3c3f)で構築した環境を前提としています.

## Harbor用PVを作成

HarborをHelmでインストールする前に,Harborが利用するPVを作成します.

今回は,個人的に管理しやすいという都合上,全てのPVC毎にSCを作成します.

PVを作る前に,PVの実際の保存先を作成します.

```bash
cd /mnt/kubernetes

sudo mkdir harbor

cd harbor

sudo mkdir chartmuseum  database  jobservice  redis  registry  trivy

chmod 777 chartmuseum  database  jobservice  redis  registry  trivy
```

PVを作成するymlをharbor-vol.ymlという名前で作成します.

内容は以下の通りです.

```yml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: harbor-registry
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: harbor-registry-pv
spec:
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: harbor-registry
  local:
    path: /mnt/kubernetes/harbor/registry
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - ノード名
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: harbor-chartmuseum
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: harbor-chartmuseum-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: harbor-chartmuseum
  local:
    path: /mnt/kubernetes/harbor/chartmuseum
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - ノード名
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: harbor-jobservice
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: harbor-jobservice-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: harbor-jobservice
  local:
    path: /mnt/kubernetes/harbor/jobservice
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - ノード名
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: harbor-database
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: harbor-database-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: harbor-database
  local:
    path: /mnt/kubernetes/harbor/database
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - ノード名
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: harbor-redis
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: harbor-redis-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: harbor-redis
  local:
    path: /mnt/kubernetes/harbor/redis
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - ノード名
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: harbor-trivy
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: harbor-trivy-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: harbor-trivy
  local:
    path: /mnt/kubernetes/harbor/trivy
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - ノード名
```

レジストリのPVは,PVCデフォルトの5GBだと少ない気がしたので,20GBに増やしています.

レジストリのPV以外は,PVCに指定されていた容量を指定しています.

このymlをapplyします.

```bash
kubectl apply -f harbor-vol.yml
```

次に,HarborのHelmリポジトリを追加します.

```bash
helm repo add harbor https://helm.goharbor.io
```

実際に,Harborをインストールします.

```bash
helm install --create-namespace -n harbor-system harbor harbor/harbor \
  --set expose.type=nodePort --set expose.tls.enabled=false \
  --set persistence.persistentVolumeClaim.registry.storageClass=harbor-registry --set persistence.persistentVolumeClaim.registry.size=20Gi \
  --set persistence.persistentVolumeClaim.chartmuseum.storageClass=harbor-chartmuseum \
  --set persistence.persistentVolumeClaim.jobservice.storageClass=harbor-jobservice \
  --set persistence.persistentVolumeClaim.database.storageClass=harbor-database \
  --set persistence.persistentVolumeClaim.redis.storageClass=harbor-redis \
  --set persistence.persistentVolumeClaim.trivy.storageClass=harbor-trivy \
  --set externalURL=http://tailscaleのmagicDNS(小文字):30002 --set harborAdminPassword=パスワード
```

namespaceは,harbor-systemです.

Portainerと同じくデフォルトSCを使用する場合は,persistence.persistentVolumeClaimをセットする必要はありません.

また,レジストリのサイズをデフォルトの5GBにした場合,persistence.persistentVolumeClaim.registry.sizeの指定は不要です.

ブラウザで,

```
http://tailscaleのmagicDNS(小文字):30002
```

を開けば,ユーザー名adminと設定したパスワードでログインできます.

## テスト用プロジェクトを作成します

ログイン後,+NEW PROJECTから動作テスト用のプロジェクトを作成します.

今回は,認証のテストをしたいので,Privateなレジストリをtestという名前で作成します.

## DockerでプライベートDockerレジストリを使えるようにする

今回,NodePortのhttpでデプロイしましたが,Dockerはデフォルトでは,httpsでないとLoginしたり,Pullしたり出来ません.

なので,今回作ったレジストリを使用できるように設定する必要があります.

Linux上のDockerであれば以下の通りです.

/etc/docker/daemon.jsonを以下のように編集し,Dockerデーモンの設定を変更します.

既に設定がある場合,前の要素の末尾に『,』を追加して次の行に"insecure-registries"の要素を記述して下さい.

```json
{
    "insecure-registries": [
        "tailscaleのmagicDNS(小文字):30002"
    ]
}
```

Dockerデーモンをリスタートします.

```bash
sudo systemctl restart docker
```

Docker Desktopの場合,歯車マーク->Docker Engineと移動して,同じように既にあるinsecure-registriesに対して"tailscaleのmagicDNS(小文字):30002"を設定し,Apply&Restartを選択すれば良い.

Windows Server等の場合は,C:\ProgramData\docker\config\に,上記と同内容のdaemon.jsonを作成して下さい(動作未確認).

## Dockerでログイン

Dockerでログインし,認証が必要なプロジェクトのレジストリにPush出来るようにします.

以下のようなコマンドでログインします.

```bash
docker login -u admin -p Helmでインストール時に設定したパスワード http://tailscaleのmagicDNS(小文字):30002
```

Login Successとなれば,成功です.

## テスト用イメージをPush

適当にnginxのイメージをPullします.

```bash
docker pull nginx
```

Pushする先のtagをつけます(自分のコンテナイメージを使う時は,ビルド時に指定すれば良い).

先程,作成したtestプロジェクトのタグを付けます.

```bash
docker tag nginx tailscaleのmagicDNS(小文字):30002/test/nginx
```

Pushします.

```bash
docker push tailscaleのmagicDNS(小文字):30002/test/nginx
```

## Kubernetes(containerd)でもプライベートDockerレジストリを使えるようにする

KubernetesのCRIとして採用したcontainerdも,デフォルトではhttpsでないとPull出来ないので,今回作ったレジストリを使用できるようにcontainerdを,設定する必要があります.

以下の様に,/etc/containerd/config.tomlを編集して下さい.

追加部分は,tailscaleのmagicDNSを用いている部分のみです.

```toml
                            (前略)
    [plugins."io.containerd.grpc.v1.cri".registry]
      [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
          endpoint = ["https://registry-1.docker.io"]
        [plugins."io.containerd.grpc.v1.cri".registry.mirrors."tailscaleのmagicDNS(小文字):30002"]
          endpoint = ["http:/tailscaleのmagicDNS(小文字):30002"]
                            (後略)
```

設定を変更したら,containerdをリスタートさせます.

```bash
sudo systemctl restart containerd
```

containerdで,プライベートレジストリが使用できるか確認します.

```bash
sudo ctr image pull -u admin:パスワード tailscaleのmagicDNS(小文字):30002/test/nginx
```

問題なく,Pull出来れば,Pullしたイメージを削除します.

```bash
sudo ctr image rm tailscaleのmagicDNS(小文字):30002/test/nginx
```

## Kubernetesでレジストリの認証情報を持ったsecretを作成

HarborはPullに認証が必要なPrivateと不要なPublicレジストリがありますが,認証が必要な場合は以下のようにして,secretを作る必要があります.

また,secretは使用するnamespace毎に,作成する必要があります.

Dockerの認証情報を用いる場合は,以下の通りです.

デフォルトnamespaceに作成する場合,『-n namespace名』の部分は不要です(以降).

```bash
kubectl -n namespace名 create secret generic secret名 \
  --from-file=.dockerconfigjson=$HOME/.docker/config.json \
  --type=kubernetes.io/dockerconfigjson
```

他のレジストリの認証情報を含んだ複数の認証情報を持つsecretを作成するには,最適な方法です.

Docker環境で,サーバーのkubectlが使えない場合や,secretの認証情報を単一にしたい場合は,以下の通りです.

```bash
kubectl -n namespace名 create secret docker-registry secret名 \
  --docker-server=http://tailscaleのmagicDNS(小文字):30002  \
  --docker-username=admin \
  --docker-password=パスワード
```

## プライベートレジストリのイメージを使用したアプリケーションを実際にデプロイ

テスト用のアプリケーションとして,以下のようなymlをtest.ymlという名前で作成します.

```yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
        - name: nginx
          image: tailscaleのmagicDNS(小文字):30002/test/nginx
      imagePullSecrets:
        - name: secret名
---
apiVersion: v1
kind: Service
metadata:
  name: test-service
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
      nodePort: 32767
      protocol: TCP
  selector:
    app: test
```

[前回](https://qiita.com/Hyper-W/items/e3b81da2bfc1a82c3c3f#nodeportのサンプルを動かす)のymlのイメージ取得先を変更しています.

```bash
kubectl apply -f test.yml
```

[前回](https://qiita.com/Hyper-W/items/e3b81da2bfc1a82c3c3f#nodeportのサンプルを動かす)と同じ様に確認して問題なければ成功です.

Podのステータスが,ErrImagePullや,ImagePullBackOffになっていないことが重要です.

## 余談:DockerでマルチCPUアーキテクチャ対応のイメージをPushする

Harborは,マルチCPUアーキテクチャ対応のDockerレジストリですが,上記のDockerデーモンの設定変更は,マルチCPUアーキテクチャ対応のイメージについては無意味です.

push時にregistry.insecure=trueオプションを設定して上げる必要があります.

```bash
docker buildx build --platform linux/amd64,linux/arm64 -t tailscaleのmagicDNS(小文字):30002/プロジェクト名/イメージ名 --output=type=image,push=true,registry.insecure=true .
```