이제 본격적으로 Application 을 개발함과 동시에 해당 Application 을 Kubernetes Cluster 에 Pod 의 형태로 배포해야 할 단계에 왔습니다. Pod 가 생성되기 위해서는 Image 가 필요하고 해당 Image 를 저장하기 위해서는 Image 저장소가 필요합니다. Docker Hub 을 사용해도 되지만 Docker Hub 에 이미지를 올릴땐 모두 공개해야 하기 때문에 Credential 정보를 포함하는 Image 를 올리기에는 적당하지 않습니다. 이를 위해 Docker Private Registry 를 사용할 수 있습니다. 이번 글에서는 Docker Private Registry 를 Kubernetes Cluster 위에 설치해보도록 하겠습니다.

  • 작업폴더 설정
$ sudo mkdir -p /workspace/kubernetes/registry
$ sudo chown -R truelifer:truelifer /workspace/kubernetes
$ cd /workspace/kubernetes/registry

먼저 docker private registry 배포를 위한 폴더를 생성 후 적절한 권한을 설정해줍니다.

  • StorageClass 생성

이전 글에서 이미 만들어 놓은 StorageClass 를 사용합니다.

  • Persistent Volume 생성
$ sudo mkdir -p /workspace/data/kubernetes/pv/registry
$ sudo chmod 777 /workspace/data/kubernetes/pv/registry

우선 worker1, worker2 각각 위의 명령을 통해 docker private registry의 데이터가 저장될 폴더를 생성하고 적절한 권한을 부여합니다. 우선은 777으로 권한을 설정했으나 향후 RBAC 기반으로 전체적인 권한체계를 구성할 예정입니다.

$ vi pv-registry.yaml

vi 편집기로 pv 생성을 위한 yaml 파일을 생성합니다.

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-registry
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /workspace/data/kubernetes/pv/registry
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- worker2

10GB 의 용량으로 설정하고 하나의 pod 만 접근하기 때문에 ReadWriteOnce, pod 가 삭제되더라도 데이터 보존을 위해 Retain 전략으로 설정합니다. NodeAffinity 설정을 통해 worker2 에 pv 를 생성하도록 합니다. 즉, docker private registry pv 는 worker2 노드에 만들어지고 docker private registry pod 역시 worker2 노드에 생성되게 됩니다.

$ kubectl apply -f ./pv-registry.yaml

kubectl 명령을 통해 pv 를 생성해 줍니다.

persistentvolume/pv-registry created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

  • Persistent Volume Claim 생성
$ vi pvc-registry.yaml

vi 편집기로 pvc 생성을 위한 yaml 파일을 생성합니다.

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-registry
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 10Gi

위에서 생성한 local-storage 타입으로 10GB 의 용량의 pvc 를 생성합니다.

$ kubectl apply -f ./pvc-registry.yaml

kubectl 명령을 통해 pv 를 생성해 줍니다.

persistentvolumeclaim/pvc-registry created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

$ kubectl get pvc

생성된 pvc 의 상태를 조회해봅니다.

NAME           STATUS    VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
pvc-registry Pending local-storage 25s
$ kubectl get pv

생성된 pv 의 상태도 조회해봅니다.

NAME          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                 STORAGECLASS    REASON   AGE
pv-registry 10Gi RWO Retain Available local-storage 3m11s

VolumeBindingMode 를 WaitForFirstConsumer로 설정했기 때문에 Status 가 아직 Available 인 점을 확인할 수 있습니다.

  • TLS 인증서 생성

Docker Private Registry 는 간단한 설정( — insecure-registry)을 통해 TLS 인증없이 http 로만 통신이 가능합니다. 하지만 실무에서 repository 를 보안없이 구성한다는 것은 매우 위험한 일입니다. 인증서를 만들기 위해서는 private key 를 만들고 CSR(Certificate Signing Request)을 생성 후 공인된 발급기관(CA) 의 서명을 받아야 하지만 이를 위해서는 매년 갱신하는 프로세스를 거쳐야 하고 비용도 발생합니다. 개인프로젝트 레벨이므로 Self-Sign 을 통해 인증서를 무료로 생성해보도록 하겠습니다.

$ mkdir -p /workspace/certs/registry
$ cd /workspace/certs/registry

먼저 master 노드에서 인증서 작업을 할 폴더를 만듭니다.

$ openssl req \
-newkey rsa:4096 -nodes -keyout /workspace/certs/registry/truelifer-registry.com.key \
-x509 -days 3650 -out /workspace/certs/registry/truelifer-registry.com.crt

openssl 을 사용해 4096비트의 Private Key 를 생성 후 3650일짜리 Certificate 를 생성합니다. Private Registry 의 도메인이름은 truelifer-registry.com 으로 합니다.

Generating a RSA private key
..............................................++++
....................................................................................................................................................................................++++
writing new private key to '/workspace/certs/registry/truelifer-registry.com.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:truelifer-registry.com
Email Address []:

다른 부분은 생략가능하지만 CN 에 truelifer-registry.com 을 입력합니다. 향후 각 노드에서 이 도메인으로 접속하기 위해서는 DNS 사용 대신 /etc/hosts 을 이용하도록 할 예정입니다.

$ ls /workspace/certs/registry

정상적으로 실행되었다면 truelifer-registry.com.crt truelifer-registry.com.key 두 파일이 생성되어 있을 것입니다. 이 두 파일을 worker1 노드와 worker2 노드에 복사해주어야 합니다.

$ sudo mkdir -p /workspace/certs/registry
$ sudo chown -R truelifer:truelifer /workspace/certs
$ cd /workspace/certs/registry

worker1, worker2 노드에 위의 명령어을 실행하여 인증서와 key파일이 저장될 폴더를 생성합니다.

저는 winscp 라는 툴을 통해 master 노드에 있는 인증서와 key 파일을 worker1, worker2 노드에 복사해주었습니다.

$ sudo mkdir -p /etc/docker/certs.d/truelifer-registry.com:8090
$ sudo cp /workspace/certs/registry/truelifer-registry.com.crt /etc/docker/certs.d/truelifer-registry.com:8090/ca.crt
  • TLS 인증서 적용

위에서 생성한 TLS 인증서를 기반으로 docker private registry 가 kubernetes cluster 상에 띄워질 것입니다. 이 registry 에 접속하려는 Client 는 위에서 생성한 TLS 인증서를 가지고 접속요청을 해야 합니다. 이를 위해 TLS 인증서를 OS(Ubuntu) 레벨과 Docker Daemon 레벨에 적용하는 과정을 진행하겠습니다. 이 과정은 master, worker1, worker2 모든 노드에 동일하게 적용해야합니다.

$ sudo cp /workspace/certs/registry/truelifer-registry.com.crt /usr/share/ca-certificates

먼저 인증서를 /usr/share/ca-certificates 폴더에 복사합니다. 폴더 경로는 os 에 따라 다를 수 있습니다.

$ sudo vi /etc/ca-certificates.conf

ca-certificates.conf 파일의 맨 마지막에 truelifer-registry.com.crt 라인을 추가한 후 저장합니다.

$ sudo update-ca-certificates

인증서를 업데이트 해줍니다.

Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.

정상적으로 추가되었다면 위와 같은 메세지가 출력됩니다.

인증서는 os 레벨 뿐 아니라 docker daemon 에게도 적용해주어야 합니다.

$ sudo mkdir -p /etc/docker/certs.d/truelifer-registry.com:8090
$ sudo cp /workspace/certs/registry/truelifer-registry.com.crt /etc/docker/certs.d/truelifer-registry.com:8090/ca.crt

위의 명령어를 실행하여 docker daemon 이 인증서를 신뢰하도록 합니다. 이 메커니즘이 작동되는 원리는 이 링크를 참조하면 됩니다. 경로는 반드시 /etc/docker/certs.d/{도메인이름:port} 이어야 하며 root 인증서 파일이름은 ca.crt 이어야 합니다.

  • Configmap 생성

docker private registry 이미지는 몇 가지 환경변수로 설정할 수 있는 방법을 제공합니다. 이 설정들을 Deployment yaml 파일에 설정해도 되지만 배포 Context 라던지 상황에 따라 Variation 이 존재할 수 있으므로 유연한 설정을 위해 Configmap 으로 관리하도록 하겠습니다.

$ kubectl create configmap configmap-registry --from-literal REGISTRY_HTTP_ADDR=0.0.0.0:8090 --from-literal REGISTRY_HTTP_TLS_CERTIFICATE=/certs/truelifer-registry.com.crt --from-literal REGISTRY_HTTP_TLS_KEY=/certs/truelifer-registry.com.key

http address, certificate와 private key 의 위치를 지정하고 Configmap 으로 생성합니다.

configmap/configmap-registry created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

  • Deployment 생성
$ vi deployment-registry.yaml

vi 편집기로 deployment 생성을 위한 yaml 파일을 생성합니다.

apiVersion: apps/v1
kind: Deployment
metadata:
name: registry
spec:
selector:
matchLabels:
app: registry
strategy:
type: Recreate
template:
metadata:
labels:
app: registry
spec:
containers:
- image: registry:latest
name: registry
env:
- name: REGISTRY_HTTP_ADDR
valueFrom:
configMapKeyRef:
name: configmap-registry
key: REGISTRY_HTTP_ADDR
- name: REGISTRY_HTTP_TLS_CERTIFICATE
valueFrom:
configMapKeyRef:
name: configmap-registry
key: REGISTRY_HTTP_TLS_CERTIFICATE
- name: REGISTRY_HTTP_TLS_KEY
valueFrom:
configMapKeyRef:
name: configmap-registry
key: REGISTRY_HTTP_TLS_KEY
ports:
- containerPort: 8090
name: registry
volumeMounts:
- name: volume-registry
mountPath: /var/lib/registry
- name: certs
mountPath: /certs
volumes:
- name: volume-registry
persistentVolumeClaim:
claimName: pvc-registry
- name: certs
hostPath:
path: /workspace/certs/registry

위에서 생성한 pv, pvc, configmap, secret 을 기반으로 deployment yaml 파일을 작성합니다. 추가로, TLS 적용을 위해 certs 라는 hostPath 기반 volume 을 생성하여 마운트해 줍니다.

$ kubectl apply -f ./deployment-registry.yaml

kubectl 명령을 통해 deployment 를 생성해 줍니다.

deployment.apps/registry created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

$ kubectl get pods

생성된 pod 를 조회해봅니다.

NAME                     READY   STATUS    RESTARTS   AGE
registry-78b9dbcf7b-hzsjz 1/1 Running 0 108s

Status 가 Running 상태로 정상적으로 배포되었음을 확인할 수 있습니다.

  • Service 생성

Deployment 로 생성한 Pod 에 접근하기 위해 Service 를 생성합니다.

$ vi service-registry.yaml

vi 편집기로 service 생성을 위한 yaml 파일을 생성합니다.

apiVersion: v1
kind: Service
metadata:
name: registry
spec:
type: ClusterIP
ports:
- port: 8090
targetPort: 8090
selector:
app: registry

보안을 위해 registry 를 Kubernetes Cluster 내부에서만 접근 가능하도록 Service 의 Type 을 ClusterIP 로 설정합니다.

$ kubectl apply -f ./service-registry.yaml

kubectl 명령을 통해 service 를 생성해 줍니다.

service/service-registry created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

$ kubectl get services

생성된 service를 조회해봅니다.

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
registry ClusterIP 10.105.202.1 <none> 8090/TCP 35s

Cluster IP 타입으로 service 가 정상적으로 생성되었음을 확인할 수 있습니다.

  • Host 파일 수정

위에서 Docker Private Registry 의 접속도메인을 truelifer-registry.com 으로 설정했습니다. 이는 도메인이름으로 실제로 접속하기 위해서는 IP 로 변환해주는 DNS 서비스가 필요합니다. 하지만 현재 별도의 DNS 서버가 셋업되어있지 않아 간단히 host 파일을 수정하여 truelifer-registry.com 에 해당하는 IP 를 매핑해주도록 하겠습니다.

$ sudo vi /etc/hosts

vi 편집기를 통해 host 파일을 열고 맨 아랫줄에 10.105.202.1 truelifer-registry.com 라인을 추가합니다. 10.105.202.1 은 위에서 생성한 registry 서비스의 cluster ip 입니다.

  • 이미지 Push 테스트

이제 모든 설정이 완료되었습니다. curl 을 이용해 각 노드에서 정상적으로 접속이 되는지 확인해보도록 하겠습니다.

$ curl https://truelifer-registry.com:8090/v2/_catalog

curl 명령어를 통해 https 프로토콜을 사용하여 truelifer-registry.com 도메인의 8090 포트로 접속하여 repository 의 리스트를 반환하는 api 를 호출합니다.

{"repositories":[]}

아직 생성한 repository 가 없으므로 blank array 를 반환하는 위의 메세지는 정상입니다.

접속자체는 확인했으니 테스트로 이미지를 push 해 보겠습니다.

$ sudo docker pull busybox
$ sudo docker tag docker.io/busybox:latest truelifer-registry.com:8090/busybox:latest
$ sudo docker push truelifer-registry.com:8090/busybox:latest

정상적으로 push 가 되는지 확인합니다.

$ curl https://truelifer-registry.com:8090/v2/_catalog

정상적으로 push 가 완료되었다면 위 명령어를 실행합니다. 아래의 메세지가 표시된다면 정상적으로 완료된 것입니다.

{"repositories":["busybox"]}
  • 마무리

Kubernetes Cluster 내에서 사용할 Docker Private Repository 를 구성해보았습니다. 다음 글에서는 이전에 Scrapy 기반으로 구현한 Daum News Crawler 를 Kubernetes Cluster 상에 배포를 해보록 하겠습니다.

--

--

Ethan Park
Ethan Park

Written by Ethan Park

Software engineer on paternity leave.

Responses (1)