#はじめに
CNCFのIncubating ProjectsであるコンテナリポジトリHarborを検証します。Harborは、Cloud Native Registryということで、Kubernetes上で運用することができるコンテナリポジトリです。プライベートクラウドなどで独自にコンテナリポジトリが欲しい方の選択肢の一つになるかと思います。機能としては、Multi-tenant、Image replication, コンテナイメージの脆弱性チェックなどの特徴を持っています。
検証
今回の検証では、HarborをKubernetes上に構築、コンテナイメージをHarborへ格納、Kubernetesで格納したコンテナイメージをデプロイを行います。
##環境
検証に使う環境として以下の環境を使います。
- Kubernetes v1.13.2 (kubeadmで構築, Master x 1, Worker x 2)
- Helm v2.9.1
##事前準備
Harborをセットアップする事前準備として、
- Ingress Controllerのセットアップ
- HarborのNamespace
- Harborのデータを格納するPersistentVolume(PV)
を準備します。
###Ingress Controllerのセットアップ
本検証では、Harborへ外部からIngress経由でアクセスするようにします。
Ingress Controllerが設定出来ていない場合は、Ingress Controllerをデプロイします。ここでは、NGINX Ingress Controllerをデプロイします。
NGINX Ingress Controllerの詳細はこちらをご参照。
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/mandatory.yaml
次に、KubernetesのNodeのIP(192.168.0.23
)をExternal IPとして利用します。ServiceのManifest(service-nodeport.yaml
)を以下に示します。
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
externalIPs:
- 192.168.0.23
type: NodePort
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
- name: https
port: 443
targetPort: 443
protocol: TCP
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
作成したManifestをデプロイします。
$ kubectl apply -f service-nodeport.yaml
デプロイされたIngress Controllerを確認します。
$ kubectl get pod -n ingress-nginx
NAME READY STATUS RESTARTS AGE
nginx-ingress-controller-565dfd6dff-9bfcf 1/1 Running 0 4h49m
$ kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx NodePort 10.99.41.249 192.168.0.23 80:30358/TCP,443:31371/TCP 4h47m
本検証では、NGINX Ingress Controllerを使っていますが、弊社のメンバが開発しているnghttpx Ingress Controllerを利用することも可能です。
###デプロイ先のNamespaceの作成
次に、HarborをデプロイするNamespaceを作成します。
$ kubectl create ns harbor
$ kubens harbor
以降、作成したNamespace(harbor
)へPodをデプロイしていきます。
###Harborのデータを格納するPersistentVolume(PV)のセットアップ
次に、Harborのデータの保存先として、本検証ではLocal Volumeをセットアップします。HarborのデータはDBMSのPostgreSQLなどに格納されるため、一般的にDBMSで推奨されているデータ保存先であるブロックストレージを利用する方がお勧めです。ここでは、Kubernetes: Local Volumeの検証
を参考にLocal Volumeをセットアップします。
まず、KubernetesのNodeにログインしLocal Volumeの準備をします。
以下の実行例ではk8s-node1
というNodeにLocal Volumeのマウント先であるディレクトリ(/mnt/disks/vol1 ... vol5
)を作成します。
$ ssh k8s-node1
$ sudo bash
# for i in 1 2 3 4 5
do
mkdir -p /mnt/disks/vol${i}
chmod 777 /mnt/disks/vol${i}
done
# ls /mnt/disks/
vol1 vol2 vol3 vol4 vol5
# exit
$ exit
次に、Harborで必要となるchartmuseum, database, job service, redistribution, registry用のPV、PVCを作成します。
以下にそれぞれのManifestを示します。
apiVersion: v1
kind: PersistentVolume
metadata:
name: chartmuseum-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/vol1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node1
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: chartmuseum-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 5Gi
apiVersion: v1
kind: PersistentVolume
metadata:
name: database-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/vol2
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node1
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: database-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 1Gi
apiVersion: v1
kind: PersistentVolume
metadata:
name: jobservice-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/vol3
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node1
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: jobservice-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 1Gi
apiVersion: v1
kind: PersistentVolume
metadata:
name: redis-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/vol4
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node1
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: redis-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 1Gi
apiVersion: v1
kind: PersistentVolume
metadata:
name: registry-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/vol5
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node1
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: registry-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 5Gi
上記のManifestをデプロイします。
$ kubectl create -f chartmuseum.pv.yaml
$ kubectl create -f chartmuseum.pvc.yaml
$ kubectl create -f database.pv.yaml
$ kubectl create -f database.pvc.yaml
$ kubectl create -f jobservice.pv.yaml
$ kubectl create -f jobservice.pvc.yaml
$ kubectl create -f redis.pv.yaml
$ kubectl create -f redis.pvc.yaml
$ kubectl create -f registry.pv.yaml
$ kubectl create -f registry.pvc.yaml
作成したPV, PVCを確認します。
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
chartmuseum-pv 5Gi RWO Delete Bound harbor/chartmuseum-claim local-storage 3m16s
database-pv 1Gi RWO Delete Bound harbor/database-claim local-storage 2m59s
jobservice-pv 1Gi RWO Delete Bound harbor/jobservice-claim local-storage 2m48s
redis-pv 1Gi RWO Delete Bound harbor/redis-claim local-storage 2m28s
registry-pv 5Gi RWO Delete Bound harbor/registry-claim local-storage 2m11s
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
chartmuseum-claim Bound chartmuseum-pv 5Gi RWO local-storage 4m17s
database-claim Bound database-pv 1Gi RWO local-storage 4m1s
jobservice-claim Bound jobservice-pv 1Gi RWO local-storage 3m50s
redis-claim Bound redis-pv 1Gi RWO local-storage 3m28s
registry-claim Bound registry-pv 5Gi RWO local-storage 3m9s
##Harborのデプロイ
いよいよ、Harborをデプロイします。
まず、始めにGitHubからharbor-helmをダウンロードします。
$ git clone https://github.com/goharbor/harbor-helm.git
$ cd harbor-helm
次に、values.yamlを編集します。変更箇所を以下に示します。
Ingressのhostのドメイン名には、ワイルドカードDNSの(xip.io
)を使います。
本検証では、coreサービスのホスト名にcore.harbor.192.168.0.23.xip.io
を notaryサービスのホスト名にnotary.harbor.192.168.0.23.xip.io
をそれぞれ指定します。externalURLも同様です。
PVCには、先程作成したLocal VolumeのPVCをそれぞれ指定します。
diff --git a/values.yaml b/values.yaml
index 3d49de5..b0f6653 100644
--- a/values.yaml
+++ b/values.yaml
@@ -24,8 +24,8 @@ expose:
commonName: ""
ingress:
hosts:
- core: core.harbor.domain
- notary: notary.harbor.domain
+ core: core.harbor.192.168.0.23.xip.io
+ notary: notary.harbor.192.168.0.23.xip.io
annotations:
ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
@@ -76,7 +76,7 @@ expose:
# the IP address of k8s node
#
# If Harbor is deployed behind the proxy, set it as the URL of proxy
-externalURL: https://core.harbor.domain
+externalURL: https://core.harbor.192.168.0.23.xip.io
# The persistence is enabled by default and a default StorageClass
# is needed in the k8s cluster to provision volumes dynamicly.
@@ -93,7 +93,7 @@ persistence:
persistentVolumeClaim:
registry:
# Use the existing PVC which must be created manually before bound
- existingClaim: ""
+ existingClaim: registry-claim
# Specify the "storageClass" used to provision the volume. Or the default
# StorageClass will be used(the default).
# Set it to "-" to disable dynamic provisioning
@@ -102,13 +102,13 @@ persistence:
accessMode: ReadWriteOnce
size: 5Gi
chartmuseum:
- existingClaim: ""
+ existingClaim: chartmuseum-claim
storageClass: ""
subPath: ""
accessMode: ReadWriteOnce
size: 5Gi
jobservice:
- existingClaim: ""
+ existingClaim: jobservice-claim
storageClass: ""
subPath: ""
accessMode: ReadWriteOnce
@@ -116,7 +116,7 @@ persistence:
# If external database is used, the following settings for database will
# be ignored
database:
- existingClaim: ""
+ existingClaim: database-claim
storageClass: ""
subPath: ""
accessMode: ReadWriteOnce
@@ -124,7 +124,7 @@ persistence:
# If external Redis is used, the following settings for Redis will
# be ignored
redis:
- existingClaim: ""
+ existingClaim: redis-claim
storageClass: ""
subPath: ""
accessMode: ReadWriteOnce
設定したvalues.yamlを使いHarborをデプロイします。
$ helm install --name home-harbor .
...
==> v1beta1/Ingress
NAME HOSTS ADDRESS PORTS AGE
home-harbor-harbor-ingress core.harbor.192.168.0.23.xip.io,notary.harbor.192.168.0.23.xip.io 80, 443 2s
NOTES:
Please wait for several minutes for Harbor deployment to complete.
Then you should be able to visit the Harbor portal at https://core.harbor.192.168.0.23.xip.io.
For more details, please visit https://github.com/goharbor/harbor.
以上で、Harborのデプロイが完了です。
##KubernetesのコンテナリポジトリをHarborに変更
次に、デプロイしたHarborをKubernetesから利用できる設定します。
Harbor WebUIへログイン
Webブラウザを開き、HarborのWebUIへログインします。
URLは先程externalURL https://core.harbor.192.168.0.23.xip.io
です。
初期のアカウントは以下です。
- User Name: admin
- Password: Harbor12345
ca.crtを取得
KubernetesのNodeへ設定する証明書(ca.crt)を取得します。
HarborのWebUIで、Configuration->System Settingsへ進み、Registry Root Certificate のDownload
をクリックし、ca.crtをダウンロードします。
KubernetesのNodeへca.crtを配置
続いて、ダウンロードしたca.crtをKubernetesの全Nodeにコピーします。
以下は、k8s-masterのNodeでの配置例です。全てのNodeに同じようにca.crtを配置します。
$ scp ca.crt k8s-master:/tmp
Nodeにログインし、/etc/docker/certs.d/[Harborのホスト名]
ディレクトリを作成し、ca.crtをコピーします。その後、dockerを再起動します。
$ ssh k8s-master
$ sudo mkdir -p /etc/docker/certs.d/core.harbor.192.168.0.23.xip.io
$ sudo mv /tmp/ca.crt /etc/docker/certs.d/core.harbor.192.168.0.23.xip.io
$ sudo systemctl restart docker
これで、ca.crtの配置は完了しました。
Secretの作成
続いて、Harborにアクセスするユーザ情報をKubernetes上にデプロイします。
ここでは、検証のため初期ユーザ(admin
)を設定していますが、環境にあわせて適宜変更してください。
core.harbor.192.168.0.23.xip.io
のユーザ情報をSecretとして保存します。
$ kubectl create secret docker-registry harbor \
--docker-server=https://core.harbor.192.168.0.23.xip.io \
--docker-username=admin \
--docker-email="" \
--docker-password='Harbor12345'
以上で、Harborに格納されたコンテナイメージをKubernetesへデプロイできる環境が整いました。
コンテナイメージの格納とKubernetesへのデプロイ
では、構築したHarborを使ってみたいと思います。
Harborにプロジェクトを作成
まず、Harborへコンテナイメージを格納する前に、格納先のプロジェクトをHarborに作成します。
HarborのWebUIにログインし、+NEW PROJECT
をクリックします。
ダイアログに、プロジェクト名を入力します。
ここではプロジェクト名sandbox
をPublicリポジトリとして作成します。
プロジェクトが作成されるとProjectsに、作成したsandbox
プロジェクトが表示されます。
###コンテナイメージをHarborへPush
次に、コンテナイメージをHarborへ格納(Push)していきます。
本検証では、適当なコンテナイメージをDocker HubからPullしてきます。
$ docker pull ubuntu:18.10
Pullしたコンテナイメージにtagをつけます。
このとき、[HarborのCoreサービスのアドレス]/[プロジェクト名]/[イメージ名:タグ名]
としてください。
$ docker tag ubuntu:18.10 core.harbor.192.168.0.23.xip.io/sandbox/ubuntu:18.10
コンテナイメージをdockerコマンドにてHarborへログインし、Pushします。
$ docker login core.harbor.192.168.0.23.xip.io
$ docker push core.harbor.192.168.0.23.xip.io/sandbox/ubuntu:18.10
これで、コンテナイメージがHarborへPushされました。
HarborのWebUIでコンテナイメージ(ubuntu:18.10
)がPushされていることを確認します。コンテナイメージが正しくHarborにPushされているかと思います。
###Harborからコンテナイメージをデプロイ
次に、KubernetesへHarborに格納したコンテナイメージをデプロイしてみます。
まず、先ほどHarborへPushしたコンテナイメージ(ubuntu:18.10
)を使ったPodのMainfestを作成します。
apiVersion: v1
kind: Pod
metadata:
name: ubuntu
spec:
containers:
- name: ubuntu
image: core.harbor.192.168.0.23.xip.io/sandbox/ubuntu:18.10
command:
- sleep
- infinity
.spec.containers.imageに、先ほどPushしたHarborのコンテナイメージを指定します。作成したManifestをデプロイし確認します。
$ kubectl create -f ubuntu-pod.yaml
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
ubuntu 1/1 Running 0 58s
$ kubectl describe pod ubuntu
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 85s default-scheduler Successfully assigned default/ubuntu to k8s-node2
Normal Pulling 84s kubelet, k8s-node2 pulling image "core.harbor.192.168.0.23.xip.io/sandbox/ubuntu:18.10"
Normal Pulled 80s kubelet, k8s-node2 Successfully pulled image "core.harbor.192.168.0.23.xip.io/sandbox/ubuntu:18.10"
Normal Created 80s kubelet, k8s-node2 Created container
Normal Started 80s kubelet, k8s-node2 Started container
kubectl describeコマンドで確認すると、Events
にHarborからコンテナイメージがPullされたことが確認できます。
以上で、HarborへコンテナイメージをPushし、Kubernetesで利用することが確認できました。
コンテナイメージの脆弱性チェック
おまけの検証として、Harborが備える機能のうち、コンテナイメージの脆弱性チェックを試してみます。
コンテナイメージは、常に注意しておかないとセキュリティパッチの当たっていないコンテナイメージを使ってしまいセキュリティホールになりかねません。そこで、コンテナイメージの脆弱性をチェックすることが重要です。HarborではClairのデータベースを利用して、脆弱性チェックを行います。
HarborのWebUIにログインし、Projectsからコンテナイメージをクリックします。
すると、左上に、SCANのボタンがあるので、対象のコンテナイメージを選択し、SCANをクリックします。すると、脆弱性のチェックを行ってくれます。
Vulnerabilityにフォーカスをあてると、詳細が確認できます。
また、この脆弱性チェックは、手動で実行するだけでなく決まった時刻に全イメージのチェックを行う設定もできます。
さらには、各ProjectsのConfigurationにコンテナイメージをPushした際に、自動で脆弱性チェックを行う設定も可能です。
#おわりに
本検証では、Harborの動作を試してみました。パブリッククラウドで公開されているコンテナイメージを利用するのに抵抗がある組織も数多くあるかと思います。
Harborは、2018年にCNCFのIncubating Projectsに昇格するなどプロジェクトとして、勢いのあるコンテナリポジトリです。プライベートクラウドに専用のコンテナリポジトリが欲しい人には、一つの選択肢になるのではないでしょうか。
また、コンテナイメージの脆弱性チェックなどセキュリティ面も考慮した機能もあります。プライベートクラウドでコンテナリポジトリを運用すると、ついついプライベートクラウドで作ったコンテナイメージだから安全と思い込みがちになってしまいます。そんなことは決してありません。コンテナイメージのベースが古いままだとセキュリティホールの温床となります。自分が普段利用するPCなどのウィルスチェックやOSのセキュリティパッチは注意深くチェックしていても、コンテナイメージまで気が回らない人も利用者には多いかと思います。より安全にコンテナイメージを管理・運用するために、定期的なコンテナイメージの脆弱性チェックをお勧めします。
昨今、巷でおうちKubernetesが流行っていますが、ご家庭でもKubernetesだけでなく、ITリテラシーの高い運用をやってみるのは如何でしょうか。