프라이빗 컨테이너 이미지 저장소 HA(High Available) 로 구성하기 (feat. Harbor)
컨테이너 이미지 저장소를 독립적으로 구성하는 방법을 살펴본다.
일반적으로 컨테이너 이미지는 CNCF 의 프로젝트 중에 하나인 Harbor 를 사용하여 구성한다. Helm chart 가 잘 되어 있어 쿠버네티스 위에서 설치하는 것은 매우 쉬운데 운영 환경에 걸맞는 HA 구성은 여러가지 고려해야 할 사항들이 있다.
그래서 이번에는 Harbor 를 HA 로 구성하는 방법을 알아본다.
Harbor HA Architecture
Harbor 를 HA 로 구성하려면 아래의 전제 조건이 필요하다.
- Kubernetes cluster 1.10 이상
- Helm 2.8.0 이상
- High Available 하게 설치된 Ingress Controller
- High Available 하게 설치된 PostgreSQL 데이터베이스
- High Available 하게 설치된 Redis
- Shared storage 로 된 PVC
이 중에서 1, 2, 3 번은 구성되어 있다고 가정하고 이후를 살펴본다.
또한 Public Cloud 는 AWS 를 사용했고 쿠버네티스 클러스터는 EKS 를 사용했다.
아키텍처는 아래 그림과 같다.
[출처: https://goharbor.io/docs/1.10/img/ha.png]
PostgreSQL 데이터 베이스 구성
AWS 의 RDS 를 사용했으며 Harbor 에서 사용할 필요한 user 생성과 권한을 부여한다.
# psql -U postgres
postgres=# CREATE DATABASE registry;
postgres=# CREATE USER harbor WITH ENCRYPTED PASSWORD 'xxxxxxx';
postgres=# GRANT ALL PRIVILEGES ON DATABASE registry TO harbor;
어드민 권한으로 데이터베이스에 접속하여 Harbor 에서 사용할 registry
database 를 생성한다. user 는 harbor
이고 필요한 password 를 생성한 다음 registry 데이터베이스의 모든 권한을 harbor 유저에 부여한다.
테이블과 스퀀스를 다루기 위해서는 아래 추가적으로 권한을 부여해야 한다. (아래 권한이 추가되지 않으면 harbor 유저로 테이블과 시퀀스에 대한 생성/조회/삭제/수정을 하지 못한다.
postgres=# \c registry
registry=# GRANT ALL ON ALL TABLES IN SCHEMA public TO harbor;
registry=# GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO harbor;
Redis 구성
Harbor 는 캐시로 레디스를 사용하며 이 때 레디스 구성은 독립 혹은 레디스 + 센티널(sentinel) 구성만을 지원한다. 한마디로 클러스터 모드의 레디스는 지원하지 않는다.
AWS Elasticache Redis 서비스는 센티널을 지원하지 않아 굳이 Elasticache 서비스를 사용할 이유가 없다.
Elasticache 서비스의 레디스 구성으로 1개의 컨트롤노드 - 멀티 워커노드 로 하여 데이터 복제는 가능하나 1개의 컨트롤 노드가 무너지면 역시 장애가 발생하므로 서비스를 사용하여 구성하지 않았다.
이 후 살펴볼 Harbor Helm chart 에서 쿠버네티스 위에 레디스를 1개로 띄우는 internal 생성 방식을 사용한다.
레디스 구성을 HA 로 하고 싶다면, 레디스를 멀티 노드 센티널 구성으로 쿠버네티스 위에 띄우는 방법도 있으니 이는 레디스 설치 문서를 참고하면 된다. (센티널 구성일 때 Harbor chart 의 value 값은 코멘트로 적혀있으니 쉽게 이해할 수 있다)
Shared Storage 구성
AWS 에서 지원하는 공유 스토리지는 EFS
가 있다. EFS 는 NFSv4
프로토콜을 지원하니 공유 스토리지로 사용 가능하다.
먼저 AWS EFS 서비스에서 파일스토리지를 생성한다.
생성된 EFS 는 실제로 파일시스템의 스토리지가 생성된 것은 아니다. 일종의 정보를 생성한 것이며 필요에 따라 실제 스토리지를 생성하고 할당 받는 방식이다.
쿠버네티스에서는 Provisioner
, StroageClass
PVC
, PV
라는 스토리지 표준 관리 방법이 있다.
흔히 Provisioner 라고 말하는 CSI Driver
를 설치한다.
EKS 에서는 추가 기능으로 Amazon EFS CSI Driver
를 추가할 수 있다.
이 때 권한에서 중요한 한가지가 있는데 EKS node 에서 사용하는 role 에 (role 명은 eks 의 태그 정보를 확인해 보면 된다) AmazonEFSCSIDriverPolicy
정책이 반드시 추가되어 있어야 한다.
이제 스토리지 클래스를 설치하자.
$ curl -Lo efs-sc.yaml https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/examples/kubernetes/dynamic_provisioning/specs/storageclass.yaml
$ vi efs-sc.yaml
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: taco-efs-storage
provisioner: efs.csi.aws.com
parameters:
provisioningMode: efs-ap
fileSystemId: fs-xxxxxxx # EFS 에서 생성한 fs id
directoryPerms: "700"
$ kubectl apply -f efs-sc.yaml
변경해야 할 것은 fileSystemId
로 앞서 EFS 에서 생성한 파일스토리지의 fs id
값으로 변경해 준다.
스토리지클래스가 잘 작동하는지 확인하기 위해서 아래와 같이 테스트 파드를 생성해 본다.
$ kubectl create ns harbor-ask
$ vi efs-test-pod.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: efs-claim
namespace: harbor-ask
spec:
accessModes:
- ReadWriteMany
storageClassName: taco-efs-storage
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Pod
metadata:
name: efs-app
namespace: harbor-ask
spec:
containers:
- name: app
image: centos
command: ["/bin/sh"]
args: ["-c", "while true; do echo $(date -u) >> /data/out; sleep 5; done"]
volumeMounts:
- name: persistent-storage
mountPath: /data
volumes:
- name: persistent-storage
persistentVolumeClaim:
claimName: efs-claim
$ kubectl apply -f efs-test-pod.yaml
Pod 가 생성되고 Pod 로 접속하면 /data/out
파일에 시간이 출력되고 있으면 정상적으로 작동하는 것이다.
PVC 를 생성할 때 accessModes
가 ReadWriteMany
인 것도 확인하자.
Harbor chart 로 HA 설치
이제 필요한 사전 구성은 마쳤으니 chart 로 설치를 진행한다.
먼저 chart 를 등록하고 다운 받는다.
$ helm repo add harbor https://helm.goharbor.io
$ helm repo update
$ helm fetch harbor/harbor --untar
차트를 다운 받을 필요는 없으니 관련 values.yaml 을 확인하기 위해서 참고용으로 다운 받았다.
필요한 value 를 설정한다.
$ vi ask-values.yaml
---
harborAdminPassword: "xxxxx"
expose:
type: ingress
tls:
enabled: true
certSource: secret
secret:
secretName: "taco-cat-tls"
ingress:
hosts:
core: harbor.xxx
className: "nginx"
externalURL: https://harbor.xxx
harborAdminPassword
는 Harbor 웹 화면에서 admin
계정으로 접속할 때 필요한 패스워드 이다.
expose
는 ingress 에 노출되는 값이며 도메인이 harbor.xxx
으로 DNS 에 연결되어 있으며 (DNS 가 없으면 로컬 컴퓨터에 /etc/hosts
파일에 등록해서 사용한다), 도메인 인증서는 앞에서 생성한 harbor-ask
네임스페이스에 taco-cat-tls
라는 secret 이름으로 저장되어 있다.
persistence:
enabled: true
resourcePolicy: "keep"
persistentVolumeClaim:
registry:
storageClass: "taco-efs-storage"
accessMode: ReadWriteMany
size: 1024Gi
jobservice:
jobLog:
storageClass: "taco-efs-storage"
accessMode: ReadWriteMany
size: 128Gi
redis:
storageClass: "taco-efs-storage"
accessMode: ReadWriteMany
size: 256Gi
trivy:
storageClass: "taco-efs-storage"
accessMode: ReadWriteMany
size: 128Gi
imageChartStorage:
disableredirect: false
type: filesystem
filesystem:
rootdirectory: /storage
persistence
는 Harbor 컴포넌트에서 사용하는 스토리지 정보이다. Harbor 차트에서 설치하는 컴포넌트는 registry
, jobservice
, redis
, trivy
, core
, portal
등이 있으며 스토리지가 필요한 컴포넌트만 기술하면 된다.
registry:
replicas: 2
portal:
replicas: 2
core:
replicas: 2
jobservice:
replicas: 2
trivy:
enabled: true
replicas: 2
notary:
enabled: false
cache:
enabled: true
expireHours: 24
각 컴포넌트의 Pod 갯수를 넣는다. HA 구성 이므로 최소 2 이상을 넣는다.
notary
는 이미지 서명 관련 컴포넌트로 이번 구성에서는 설치하지 않았다.
database:
type: external
external:
host: xxxxxx.ap-northeast-2.rds.amazonaws.com
port: "5432"
username: "harbor"
password: "xxxxxxx"
coreDatabase: "registry"
RDS 에 만들어진 외부 데이터베이스 정보를 넣는다.
redis:
type: internal
레디스는 내부에서 단일 Pod 로 생성한다.
전체 values 는 다음과 같다.
$ vi ask-ha-values.yaml
---
harborAdminPassword: "xxxxxx"
expose:
type: ingress
tls:
enabled: true
certSource: secret
secret:
secretName: "taco-cat-tls"
ingress:
hosts:
core: harbor.xxx
className: "nginx"
externalURL: https://harbor.xxx
persistence:
enabled: true
resourcePolicy: "keep"
persistentVolumeClaim:
registry:
storageClass: "taco-efs-storage"
accessMode: ReadWriteMany
size: 1024Gi
jobservice:
jobLog:
storageClass: "taco-efs-storage"
accessMode: ReadWriteMany
size: 128Gi
redis:
storageClass: "taco-efs-storage"
accessMode: ReadWriteMany
size: 256Gi
trivy:
storageClass: "taco-efs-storage"
accessMode: ReadWriteMany
size: 128Gi
imageChartStorage:
disableredirect: false
type: filesystem
filesystem:
rootdirectory: /storage
registry:
replicas: 2
portal:
replicas: 2
core:
replicas: 2
jobservice:
replicas: 2
trivy:
enabled: true
replicas: 2
notary:
enabled: false
cache:
enabled: true
expireHours: 24
database:
type: external
external:
host: xxxxxx.ap-northeast-2.rds.amazonaws.com
port: "5432"
username: "harbor"
password: "xxxxxxx"
coreDatabase: "registry"
redis:
type: internal
쿠버네티스에 배포한다.
$ helm upgrade -i harbor-ask harbor/harbor --version 1.12.3 -n harbor-ask -f ask-ha-values.yaml
설치 확인
Harbor 웹에 접속하여 사용자(tks)와 프로젝트(tks)를 만든다.
해당 프로젝트의 Members
탭에는 사용자가 등록되어 있어야 한다. (그래야 컨테이너 이미지를 올릴 수 있는 권한이 있다)
$ docker login harbor.xxx -u tks
Password:
$ docker pull hello-world
$ docker tag hello-world harbor.xxx/tks/hello-world
$ docker push harbor.xxx/tks/hello-world