[Kubernetes-Container Registry] 쿠버네티스 클러스터에 사설 컨테이너 레지스트리 구축하기

2020. 1. 21. 11:32Engineering/Container&Kubernetes

Content

1. Overview
2. 클러스터 준비하기
3. Container Registry 띄우기
4. SSL 인증서 생성
5. SSL 인증서 설치
6. 이미지 PUSH 해보기


 

쿠버네티스 클러스터에 Private Container Registry를 구축해 봅니다. 


 

1. Overview

지난번에 단일 노드 상에서 Docker 만을 사용하여 Private Registry 구축하는 법을 포스팅했는데요.

이번엔 여러 노드로 구성된 클러스터 상에서 Kubernetes 사용하여 Private Registry 구축하는 방법 대해 알아보고자 합니다.

 

 

클러스터 상에 레지스트리를 구축할 때는 이미지들을 저장할 볼륨에 신경을 써주셔야 합니다.

이유를 생각해볼까요?

크게 가지 이유를 있습니다.

 

 

 

 

1. Container 볼륨은 휘발성이다.

 

컨테이너의 내부 파일 시스템은 휘발성 디스크 입니다.

컨테이너가 죽으면 데이터도 모두 사라지게 됩니다.

(https://kubernetes.io/docs/concepts/storage/volumes/)

 

 

그러나, 컨테이너 레지스트리는 업로드된 컨테이너 이미지들을 저장하고 있어야 합니다.

 

 

아래 그림처럼 어떤 장애에 의해서

컨테이너 레지스트리 서버 역할을 하던 컨테이너가 재시작 된 상황을 가정해 봅시다.

 

왼쪽의 그림에서 레지스트리에 업로드되었던 파일들이,

레지스트리 컨테이너가 재시작됨에 따라 오른쪽 그림에서는 날아가게 됩니다.

 

레지스트리 사용자 입장에서 볼 때, 이전에 있던 이미지들이 어느 순간 사라지게 된거죠.

이는 사용자들이 레지스트리 서비스에 기대하던 모습이 아닙니다.

 


따라서 안정적인 레지스트리 서버 제공을 위해서는.

POD(또는 컨테이너) 재시작에 관계없이 레지스트리 이미지 데이터들을 유지하기 위한 조치가 필요합니다.

 

 

 

 

2. POD 클러스터 내에서 노드를 옮겨 다닌다.

 

일반적으로 Kubernetes에서는 어플리케이션 POD들의 장애 복구를 자동으로 수행합니다.

 

예를 들어, 특정 노드에 장애가 있어 노드 위에 있던 POD들이 down 됐다?

그러면 Kubernetes 장애가 발생한 노드 외에 다른 노드 위에 POD를 새로 띄웁니다.

이 때문에, POD Kubernetes 클러스터 안에서 노드들을 옮겨 다니는 것이 일반적입니다.

 

그런데 위에서 말했듯이 레지스트리 서버는 컨테이너 이미지들을 잘 유지해야 합니다.

 

만약 아래 그림에서 WorkerNode#1에 장애가 발생하면,

WorekerNode#2에 새로운 POD가 생성될 것입니다.

 

그러나 이전의 POD에 저장되어 있던 컨테이너 이미지 데이터들이

새로 띄운 POD에 저장되어 있단 보장이 없어 정상적인 레지스트리 서버 기능 제공이 불가능합니다.

 

 

따라서 안정적인 레지스트리 서버 제공을 위해서는.

레지스트리 서버 POD의 노드 간 이동에 관계없이 레지스트리 이미지 데이터들을 잃어버리지 않도록 조치가 필요합니다.

 

 

본 글에서는 위의 두 가지 이슈를 각각 해결하기 위해,

HostPath Volume과 NodeSelector 및 NAS 를 활용하겠습니다.

(물론 더 나은 방법들이 있을 수 있습니다. 더 좋은 해결방안이 있으면 공유 주세요ㅎㅎ)

 

 

1-1) HostPath Volume (Persistent Volume)

우선 Kubernetes에서 Volume이란 POD에 마운트되는 저장공간으로,

POD 내부의 컨테이너들이 공유하는 저장공간입니다.

 

그 중 HostPath Volume은 Host의 disk를 POD에 마운트하는 Persistent Volume입니다.

POD에 마운트된 볼륨이 Host disk를 바라보고 있으므로,

POD(또는 컨테이너)가 재시작되어도 볼륨내 저장되어있던 데이터들이 사라지지 않습니다.

 

따라서 컨테이너 레지스트리에 HostPath Volume을 마운트 하여,

POD(또는 컨테이너) 재시작에 관계없이 레지스트리 이미지 데이터들을 영구적으로 보관할 수 있습니다.

 

 

2) NodeSelector 및 NAS

 

NodeSelector는 특정 어플리케이션 POD들을 어떤 Node에 배포할지 지정할 수 있는 Kubernetes 기능입니다.

어플리케이션의 label과 동일한 label을 갖는 node에만 해당 어플리케이션의 POD가 배포됩니다.

 

NAS는 disk 종류의 하나인데, 네트워크를 통해 공유되는 disk입니다.

따라서 여러 서버가 네트워크를 통해 하나의 NAS를 공유하여 사용할 수 있습니다.

 

 

자 그럼 NodeSelector와 NAS를 잘 조합해 봅시다.

먼저 레지스트리 POD들을 배포할 노드들을 지정하고,

NodeSelector 기능을 사용할 수 있도록 레지스트리 POD label과 동일한 label을 달아줍니다.

 

그 다음, 레지스트리용 노드들간 공유하는 NAS 볼륨을 생성하고,

이 NAS 볼륨을 각 레지스트리 노드의 host disk에 마운트 하고,

이 host disk를 레지스트리 POD 볼륨에 마운트 합니다.

 

 

최종적으로 아래 그림의 왼쪽과 같은 형상이 저희가 구축할 형상입니다.

 

레지스트리 POD는 항상 NAS 볼륨에 이미지 데이터를 저장하게 됩니다.

따라서 WorkerNode#1의 장애로 WorkerNode#2에 POD가 새롭게 생성되더라도,

이 POD는 NAS로부터 이미지 데이터들을 불러올 수 있습니다.

 

또한, NodeSelector 통해 Registry POD 항상

NAS볼륨이 연결된 Registry 노드들에만 배포되므로,

Registry POD 항상 NAS에서 이미지 데이터들을 불러올  있습니다.

 

 

2. 클러스터 준비하기

, 그럼 Overview 마지막 그림과 같은 형상 구축을 위해.

제일먼저 클러스터와 인프라부터 준비해봅시다.

 

2-1) 클러스터 NAS 생성

필요한 인프라는

쿠버네티스 클러스터 + NAS 입니다.

 

저는

- Master 1 (host: eyohmaster)

- Worker 2 (host: eyoh-imgregi, eyoh-worker)

이루어진 쿠버네티스 클러스터를 준비했고,

 

- Worker 2대가 공유할 NAS 볼륨

준비해 두었습니다.

(이 인프라들은 kt cloud를 서비스를 사용하였습니다.)

 

NAS 볼륨을 정상적으로 신청 하셨다면,

NAS 볼륨을 공유하기로 지정한 노드들에서 NAS 볼륨을 확인할 있습니다.

 

저는 'nas'라는 이름의 NAS 볼륨을 신청했고,

NAS 볼륨을 클러스터 모든 노드들이 공유하도록 지정했습니다.

'eyoh-worker' 노드에서 신청한 볼륨을 확인해보겠습니다.

 

$ showmount -e [NAS_IP]

 

 

 

'/nas' 볼륨이 정상적으로 조회가 됩니다.

 

 

2-2) NAS 볼륨 mount

 

NAS 볼륨을 사용할 WorkerNode들의 특정 폴더로 볼륨을 mount 해야 합니다.

 

저는 '/eyoh-creg' 라는 경로를 만들고,

경로에 NAS 볼륨을 mount 해보겠습니다.

 

$ mkdir /eyoh-creg
$ mount -t nfs 172.27.128.2:/nas /eyoh-creg

 

 

 

 

mount 명령어는 일시적인 mount 수행하므로,

서버를 재부팅하는 경우 mount 해제 됩니다.

재부팅후에도 볼륨이 자동 mount 되게 하려면 '/etc/fstab' 아래와 같은 명령줄을 추가합니다.

 

$ vi /etc/fstab
(파일 맨 마지막줄 삽입) [NAS_IP]:/[NAS_NAME] /[MOUNT_PATH] nfs rw 0 0

 

 

 

방법과 마찬가지로,

Container Registry 배포용으로 쓰일 모든 노드들에

NAS 볼륨을 각각 마운트 해줍니다.

 

저는 WorkerNode 2대를 모두 Container Registry 배포용 노드로 사용하겠습니다.

따라서  두 WorkerNode에 마운트 작업을 동일하게 수행했습니다.

 

 

 

2-3) NodeSelector사용을 위한 Node Label 추가

다음으로, NodeSelector기능 활용을 위해 Registry 배포용 노드들에 label 달아 봅시다.

 

저는 WorkerNode2대를 모두 Container Registry 배포용으로 사용할 예정이므로,

노드에 'node=registry' 이라는 label 달아보겠습니다.

 

$ kubectl label nodes [NODE_NAME] [LABEL_KEY]=[LABEL_VALUE]

 

 

Label 제대로 적용 되었는지 확인해보시구요.

$ kubectl get nodes --show-labels

 

 

여기까지 기본적인 클러스터 구성이 완료되었습니다.

 

 

 

3. Container Registry 띄우기

이제 Container Registry 띄워 봅시다.

Container Registry는 docker hub에 'registry'라는 이름의 공인된 이미지로 제공되고 있습니다.

https://hub.docker.com/_/registry

 

저는 이미지를 사용하겠습니다.

가장먼저 Replication Controller 부터 배포하겠습니다.

 

3-1) Replication Controller 띄우기

아래 yaml 파일을 그대로 사용하시면 됩니다.

registry_conf.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  creationTimestamp: "2019-11-28T05:42:39Z"
  generation: 1
  labels:
    run: registry
  name: registry
  namespace: default
  resourceVersion: "4023572"
  selfLink: /api/v1/namespaces/default/replicationcontrollers/registry
  uid: e442decf-f8fa-406b-822c-ee7cb9b23376
spec:
  replicas: 1
  selector:
    run: registry
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: registry
    spec:
      containers:
      - image: registry
        imagePullPolicy: Always
        name: registry
        ports:
        - containerPort: 5000
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        env:
        - name: REGISTRY_HTTP_ADDR
          value: "0.0.0.0:5000"
        - name: REGISTRY_HTTP_TLS_CERTIFICATE
          value: "/certs/server.crt"
        - name: REGISTRY_HTTP_TLS_KEY
          value: "/certs/server.key"
                  volumeMounts:
        - mountPath: /certs
          name: cert
        - mountPath: /var/lib/registry
          name: registry
      volumes:
      - name: cert
        hostPath:
          path: /eyoh-creg/certs
      - name : registry
        hostPath:
          path: /eyoh-creg/registry
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      nodeSelector:
        node: registry
status:
  availableReplicas: 1
  fullyLabeledReplicas: 1
  observedGeneration: 1
  readyReplicas: 1
  replicas: 1

 

주의해서 봐야할 부분만 짚고 넘어가겠습니다.

  • Container
    - image
    registry 이미지 사용
    - 5000 포트 사용
    - env registry IP SSL 인증서 경로 지정 (본 글의 4장, 5장에서 더 설명됩니다.)
  • Volume VolumeMount
    - SSL
    인증서를 local host /eyoh-creg/certs 옮겨두고,
    -
    경로를 컨테이너의 /certs 경로로 마운트
    - 이미지 데이터를 저장할 NAS 볼륨의 경로를 생성하고 /eyoh-creg/registry
    -
    경로를 컨테이너의 /var/lib/registry 마운트

  • NodeSelector
    - node=registry label을 갖는 노드에만 파드가 배포.

 

 

위 yaml 파일로 Replication Controller를 배포합니다.

$ kubectl apply -f registry_conf.yaml

 

정상적으로 배포되었는지 확인해봅니다.

$ kubectl get rc

 

3-2) 서비스 노출 시키기

 

컨테이너는 동적인 IP를 갖습니다.

컨테이너가 재시동 될 때마다 새로운 IP를 할당받게 되죠.

그러나 임의의 서비스를 위해서는 그 서비스에 접속할 IP가 고정되어야 합니다.

 

이를 위해 Kubenretes는 Service라는 객체를 사용합니다.

Service는 여러 POD들을 하나의 고정된 IP로 묶는 POD의 상위 객체 입니다.

 

Service 라는 객체에는 여러가지 type이 존재하는데요.

저희는 ClusterIP type의 service 객체를 사용할겁니다.

ClusterIP는 클러스터 내부에서만 접속 가능한 IP를 서비스 객체에 할당합니다.

따라서 Private Container Registry를 구축하려는 저희의 목적에 적합하죠!

 

Service 객체는 이미 만들어진 배포단위가 있어야 합니다.

배포단위라함은 Deployment, ReplicationController 등등 입니다.

 

그럼, 저희는 이제

3-1)에서 만들어둔 ReplicationController를 ClusterIP type의 서비스로 노출시켜 보겠습니다.

 

아래 명령어로 서비스 노출을 할 수 있습니다.

$ kubectl expose rc [REPLICATION_CONTROLLER_NAME] --type=ClusterIP --name=[SVC_NAME]

 

 

 

노출된 서비스 상태를 확인해 봅시다.

$ kubectl get svc

 

 

registry-svc라는 이름으로 서비스 객체가 정상적으로 생성 되었습니다.

이때, Cluster-IP가 할당된것을 확인하실 수 있습니다.

이 IP가 바로, registry 서비스에 접근 가능한 클러스터 내부 IP 입니다.

 

 

4. SSL 인증서 생성

 

지금까지 Container Registry를 고정 IP를 갖는 서비스 객체로 클러스터 내부에 배포하였습니다.

Container Registry를 사용하기 위한 마지막 작업이 있습니다.

 

바로, Container Registry 서비스 에 할당된 IP의  SSL 인증서를 생성하고 적용하는 것 입니다.

(Container Registry는 https 프로토콜을 사용하므로,  SSL 인증서가 필수 입니다.)

 

 

https://5equal0.tistory.com/entry/Docker-Registry-%EC%82%AC%EC%84%A4-%EC%9B%90%EA%B2%A9-%EB%A0%88%EC%A7%80%EC%8A%A4%ED%8A%B8%EB%A6%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0?category=730370

위 글의 2장을 참고하시어, SSL 인증서를 생성해주시기 바랍니다.

주의 하실 점은 3-2)에서 할당받은 ClusterIP로 인증서를 생성 하셔야 한다는 겁니다.

저의 경우 10.100.153.175 가 되겠습니다.

 

 

각 인증서 파일들은 Registry용 노드들의 host 경로에 모두 복사되어 있어야 합니다.

그래야 어떤 노드에 Reigstry POD가 띄워져도, 인증서 파일을 찾을 수 있겠죠.

 

 

그런데, 저희는 이미 Registry용 노드들이 공유하는 NAS 볼륨을 사용하고 있습니다.

따라서 저는 NAS 볼륨을 mount한  /eyoh-creg 경로 밑에,

/eyoh-creg/certs 라는 경로를 만들고 여기에 인증서 파일들을 생성 하였습니다.

따라서, 이 NAS 볼륨을 공유하고 있는 다른 노드 (eyohmaster, eyoh-imgregi)에서도 인증서 파일이 조회 가능합니다.

eyoh-master 노드에서 인증서 파일 조회

 

eyoh-imgregi에서 인증서 파일 조회

 

 

5. SSL 인증서 설치

, SSL 인증서가 생성 되었으면 적용시켜야 겠죠!

SSL인증서는 서버쪽과 클라이언트 쪽에 모두 설정이 필요합니다.

(사실 클라이언트 쪽 설정이 왜 필요한지 모르겠습니다... 이부분은 설정이 필요없도록 하는 뭔가 방법이 있을 것 같은데.. 더 알아보고 답을 찾게 되면 업데이트 하겠습니다)

 

5-1) 서버측 설정

사실 3-1) ReplicationController의 yaml파일에 SSL 인증서 적용방법이 모두 나와있습니다.

Replication Controller의 yaml파일에서 volumeMount 와 env 설정 내용만 다시 보겠습니다.

 

      containers:
      - image: registry
        imagePullPolicy: Always
        name: registry
        ports:
        - containerPort: 5000
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        env:
        - name: REGISTRY_HTTP_ADDR
          value: "0.0.0.0:5000"
        - name: REGISTRY_HTTP_TLS_CERTIFICATE
          value: "/certs/server.crt"
        - name: REGISTRY_HTTP_TLS_KEY
          value: "/certs/server.key"
        volumeMounts:
        - mountPath: /certs
          name: cert
        - mountPath: /var/lib/registry
          name: registry
      volumes:
      - name: cert
        hostPath:
          path: /eyoh-creg/certs
      - name : registry
        hostPath:
          path: /eyoh-creg/registry
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      nodeSelector:
        node: registry

 

먼저 SSL 인증서가 저장되어 있는 local 경로를 Registry 컨테이너에 마운트 해야겠죠.

이는 위 19번째 줄에 기재 되어 있습니다.

 

그 다음, 컨테이너 환경변수로 SSL 인증서 및 key파일의 위치를 입력합니다.

이는 위 11번째 줄에 기재 되어 있습니다.

(주의 하실 점은 환경변수로 전달하는 경로는 모두 컨테이너 경로 입니다. )

 

5-2) 클라이언트측 설정

클라이언트측 설정은 아래 글의 2.3 절을 참고하셔서 적용 하시기 바랍니다.
https://5equal0.tistory.com/entry/Docker-Registry-%EC%82%AC%EC%84%A4-%EC%9B%90%EA%B2%A9-%EB%A0%88%EC%A7%80%EC%8A%A4%ED%8A%B8%EB%A6%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0?category=730370

 

 

6. 이미지 PUSH 해보기

자 그럼, 클라이언트 쪽에서 이미지를 푸시 해보겠습니다.

저희는 클러스터 내에서만 접근 가능한 Private Registry를 구성하였기 때문에

클라이언트는 클러스터 내의 임의의 노드여야 겠죠?

저는 Master노드(eyohmaster)를 클라이언트로 활용 하겠습니다.

 

 

클라이언트 노드에는 docker CLI 설치와 5-2장의 SSL 인증서 설정이 완료되어 있어야 한다는 것을 잊지 마시고, 미리 환경 설정을 완료하시기 바랍니다!

 

 

자, 그럼 시작해봅시다.

저는 클라이언트로 활용할 Master노드의 로컬 저장소에 nginx 이미지를 저장해두었습니다.

 

이 이미지를 저희가 구축한 Private Registry로 PUSH하기 위해서는

이미지 tag를 [REGISTRY_IP]:[REGISTRY_PORT]/[IMG_NAME] 와 같은 형태로 설정해야합니다.

 

따라서 저는 nginx 이미지를 아래와 같이 tagging해 주었습니다.

$ docker tag [SRC_IMG_NAME] [REGISTRY_IP]:[REGISTRY_PORT]/[DST_IMG_NAME]

 

 

tag가 제대로 설정되었는지 확인해 보시구요.

$ docker images

 

 

이제 이미지를 PUSH해봅시다!

$ docker push [REGISTRY_IP]:[REGISTRY_PORT]/[DST_IMG_NAME] 

                                                                                                                

 

 

Registry에 이미지가 제대로 업로드 되었는지,

레지스트리의 이미지 목록을 출력해 봅니다.

$ curl -X GET https://[REGISTRY_IP]:[REGISTRY_PORT]/v2/_catalog

 

 

넵, 제가 nginx 이미지를 새로 tag하면서 지어준 nging_eyoh 라는 이름의 이미지가

저희가 구축한 Registry로 업로드 되었습니다.

 

 

휴우. 기나긴 글이 끝났습니다!

두서없이 급하게 써낸 글이라.. 계속해서 수정이 필요할 것 같습니다.

두서없이 긴 글을 읽어 주시느라 고생 많으셨습니다. 감사합니다!