AWS EKS를 활용한 CI/CD 구축 - 1
2023-06-06 09:05
AWS EKS를 활용하여 Jenkins, ArgoCD 등 CI/CD 파이프라인을 구축하는 데 필요한 서비스를 배포하고, 이 과정에서 맞닥뜨린 각양 각색의 문제들과 문제를 해결한 방법에 대해서 공유합니다.
개요
Docker & Kubernetes의 기본 개념을 알고 있으면 이해하기 수월합니다.
많은 기업들이 컨테이너 기반 인프라를 사용하며, 대표적인 컨테이너 오케스트레이션 툴인 쿠버네티스를 활용한다. AWS 클라우드 인프라 또한 이제는 뗄레야 뗄 수 없는 핵심 인프라로써 기능하는 곳이 많을 것이다.
AWS는 Elastic Kubernetes Service(EKS)를 통해, 쿠버네티스의 컨트롤 플레인과 노드 그룹을 AWS 리소스를 통해 자동으로 구성 및 관리해주는 편리한 '완전관리형' 서비스를 제공한다. 특히 기존 사용하던 서버 인프라가 AWS의 VPC나 서브넷 등의 네트워크 리소스를 사용하고 있다면 매우 편리하게 구성을 지원해준다.
본 포스팅은 AWS EKS와 Git, Elastic Container Registry(ECR)을 활용하여 애플리케이션의 CI/CD 파이프라인을 구성하는 과정을 소개하는 시리즈 첫 글이며, EKS 구성 및 필요 서비스 설치와 Ingress 및 네트워크 리소스 구성, 서비스 내부 설정과 실제 애플리케이션 배포로 구성된다.
구축이 완료되면 다음과 같은 인프라가 구축될 것이다.
사전 준비
로컬에 다음 4가지가 필요하다.
- awscli
- eksctl
- kubectl
- helm
설치는 맥북 기준이다.
awscli
- 설치:
brew install awscli
~/.aws/credentials
에 IAM 계정별 access key, secret access key를 등록해야 한다. AWS IAM 콘솔에서 생성한다.
eksctl
- 최신 eksctl 바이너리 다운로드:
curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
- 바이너리 이동:
mv -v /tmp/eksctl /usr/local/bin
- 설치 확인:
eksctl version
kubectl
- 다음 설치 가이드 링크를 참조하여 설치한다: https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html
- JSON 형식의 데이터를 다루는 커맨드라인 유틸리티 jq:
brew install jq
- kubectl 명령어 자동 완성:
brew install bash-completion
Helm
- 설치:
brew install helm
AWS EKS 생성
EKS에서 클러스터 생성이나 노드 그룹 생성을 위해서는, AWS 리소스가 다 그렇듯이 적절한 IAM 계정에 대한 권한 및 역할, 정책이 설정되어 있어야 한다. 생성에 들어가기 전에, 다음 링크를 통해 본인이 생성해야 할 권한 및 역할, 정책이 무엇인지 확인한 후 생성해두도록 하자: https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/security-iam.html
Cluster 생성
EKS 클러스터를 생성한다. AWS EKS 콘솔(https://ap-northeast-2.console.aws.amazon.com/eks)로 접속 -> 클러스터 추가 -> 생성을 선택한다.
클러스터 구성
- 이름: 클러스터의 이름이며, 고유해야 한다.
- Kubernetes 버전: 2023.06.03 기준 기본값은 1.27이다.
- 클러스터 서비스 역할: 상단의 Amazon EKS용 자격 증명 및 액세스 관리 가이드를 통해 적절한 IAM 설정이 완료됐다면, 여기에 나타난다.
네트워킹
- VPC / 서브넷 / 보안 그룹: 본인이 사용중인 AWS VPC 네트워크 정보를 입력한다. 잘 기억이 안난다면 AWS VPC 콘솔을 이용해보자: https://ap-northeast-2.console.aws.amazon.com/vpc
로깅
설정해두면 API 요청, 클러스터 액세스, 클러스터 인증, 클러스터 컨트롤러, 스케줄러에 대한 로그를 AWS CloudWatch 기능을 통해 제공받을 수 있다.
추가 기능
kube-proxy, Amazon VPC CNI, CoreDNS 세 가지 기능은 자동으로 설치되며 제외할 수 없다. Pod이 배포되면 kube-system 네임스페이스에 각 기능마다 2개의 Pod이 배포된다.
설정을 완료하고 생성을 선택하면 클러스터가 생성된다. 10분 정도 소요된다.
Node Group 생성
클러스터가 생성되면 EKS 콘솔에서 선택하여 상세 정보로 진입할 수 있다.
클러스터 상세 정보 -> 컴퓨팅 탭 -> 노드 그룹 추가를 선택한다.
노드 그룹 구성
- 이름: 노드 그룹의 이름이며, 고유해야 한다.
- 노드 IAM 역할: 상단의 Amazon EKS용 자격 증명 및 액세스 관리 가이드를 통해 적절한 IAM 설정이 완료됐다면, 여기에 나타난다.
컴퓨팅 및 조정 구성
노드 그룹 컴퓨팅 구성
- AMI 유형: 각 노드에 사용할 AMI 이미지를 선택한다.
- 용량 유형: On Demand 인스턴스인지, Spot성 인스턴스인지 선택.
- 인스턴스 유형: 인스턴스 유형을 선택한다. 본인의 인프라 및 서비스의 규모 등을 복합적으로 고려하여 선택하자.
- 디스크 크기: 각 노드에 연결되는 EBS 볼륨의 크기
노드 그룹 조정 구성
- 원하는 크기 / 최소 크기 / 최대 크기: 노드 그룹 인스턴스가 EKS 오토 스케일링 정책에 의해 확장될 크기를 지정한다.
노드 그룹 업데이트 구성
- 최대 사용 불가: 노드 그룹 버전 업데이트 중에 사용할 수 없는 노드의 최대 허용 수
노드 그룹 네트워크 구성
- 서브넷: 본인이 사용중인 AWS VPC 네트워크의 서브넷 정보를 입력한다. 잘 기억이 안난다면 AWS VPC 콘솔을 이용해보자: https://ap-northeast-2.console.aws.amazon.com/vpc
- 노드에 대한 원격 액세스 허용: 이 옵션을 설정하면 노드 그룹 생성 시 올라가는 노드(EC2 인스턴스)에 대한 SSH 접근이 가능해진다. EC2 키페어 및 보안 그룹을 지정해야 한다.
설정을 완료하고 생성을 선택하면 노드 그룹이 및 노드가 생성된다. 10분 정도 소요된다.
Kubeconfig
EKS 클러스터가 생성되면, kubectl을 사용하기 위해 클러스터로부터 kubeconfig.yaml을 받아오도록 하자.
aws eks update-kubeconfig --profile {my_profile} --name {my_cluster} --region {my_region} --kubeconfig ./{my_kubeconfig}.yaml
- my_profile: ~/.aws/credentials에 저장된 IAM 엑세스 토큰 프로필
- my_cluster: 생성한 클러스터 이름
- my_region: 클러스터를 생성한 AWS 리전
- my_kubeconfig: 저장할 kubeconfig 파일명
이후 kubectl 및 helm을 사용할 때 이 kubeconfig.yaml 파일을 사용할 것이다.
Ingress Controller
쿠버네티스 서비스가 외부와 통신하기 위해서는 여러 방법(NodePort, LoadBalancer, Ingress)이 있는데, Ingress Controller를 사용해보자. 일반적으로 Nginx Ingress Controller를 많이 사용하지만, 필자는 AWS Certification Manager (ACM) 인증서를 연결해야 하여 aws load balancer controller를 사용하였다. ACM 인증서는 key, pem 등을 외부로 반출할 수 없기 때문에 nginx를 이용해 인증서를 연결하기 까다롭기 때문이다.
- IAM Service Account를 생성하고, AWSLoadBalancerControllerIAMPolicy와 연결
- 해당 Role은 AWS CloudFormation Stack 템플릿을 통해 생성되고, Open ID Connect Provider를 만드는 행위이므로 관련 IAM 정책 설정이 필요하다.
"iam:GetOpenIDConnectProvider", "iam:CreateOpenIDConnectProvider", "iam:DeleteOpenIDConnectProvider", "iam:ListOpenIDConnectProviders", "iam:AddClientIDToOpenIDConnectProvider", "iam:RemoveClientIDFromOpenIDConnectProvider", "iam:UpdateOpenIDConnectProviderThumbprint" ... // 그 외 CloudFormation Stack 생성 Role 및 Role 자체를 생성하는 정책 포함
- IAM 정책이 설정되었으면 OIDC Provider를 생성
eksctl utils associate-iam-oidc-provider --region={my_region} --cluster={my_cluster} --approve
- AWSLoadBalancerControllerIAMPolicy 생성
- policy json 다운로드
curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/install/iam_policy.json
- policy 생성
aws iam create-policy \
--policy-name AWSLoadBalancerControllerIAMPolicy \
--policy-document file://iam_policy.json
- policy json 다운로드
- IAM Service Account 생성
eksctl create iamserviceaccount \
--cluster={my_cluster} \
--namespace=kube-system \
--name=aws-load-balancer-controller \
--role-name AmazonEKSLoadBalancerControllerRole \
--attach-policy-arn=arn:aws:iam::872489838303:policy/AWSLoadBalancerControllerIAMPolicy \
--region {my_region} \
--approve
- Helm을 통해 컨트롤러 설치
helm install aws-load-balancer-controller eks/aws-load-balancer-controller --set clusterName={my_cluster} --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller --set vpcId={my_vpc_id} -n kube-system --kubeconfig="./{my_kubeconfig}.yaml"
- VPC ID에 주의한다. 실제 클러스터의 VPC를 넣어야 한다.
- Ingress 생성 시 컨트롤러가 VPC의 Subnet을 찾을 수 있게 하기 위해 태그를 달아줘야 한다.
aws ec2 create-tags --resources {my_subnet_id} --tags Key=kubernetes.io/role/internal-elb,Value=1
- 컨트롤러는 EC2DescribeSubnet 액션을 통해 subnet의 정보를 가져오므로 관련 권한이 없을 경우 동작하지 못한다. 권한을 넣어주도록 하자.
Jenkins 배포
Jenkins 네임스페이스를 생성한다: kubectl create namespace jenkins --kubeconfig="./kubeconfig.yaml"
EBS 볼륨 생성
EC2 콘솔에서 Jenkins Pod용으로 EBS 볼륨을 생성한다. Jenkins Pod이 배포될 노드와 동일한 AZ(가용영역)의 볼륨만 접근 가능하므로 유의.
- jenkins-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: jenkins-pv
spec:
capacity:
storage: 30Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: failure-domain.beta.kubernetes.io/zone
operator: In
values:
- ap-northeast-2a // AZ 고정
awsElasticBlockStore:
volumeID: vol-1111111111111 // 위에서 생성한 EBS 볼륨의 ID를 넣는다.
fsType: ext4
- 생성:
--kubectl apply -f jenkins-pv.yaml --kubeconfig="./kubeconfig.yaml" --namespace=jenkins
PVC 생성
- jenkins-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jenkins-pvc
namespace: jenkins
spec:
storageClassName: ""
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 30Gi
- 생성:
kubectl apply -f jenkins-pvc.yaml --kubeconfig="./kubeconfig.yaml" --namespace=jenkins
Jenkins 배포
helm을 통해 배포한다. 다음 명령어를 통해 helm repo를 업데이트 해 둔다.
helm repo add jenkinsci https://charts.jenkins.io
helm repo update
- jenkins-values.yaml
controller:
serviceType: LoadBalancer
startupProbe:
enabled: true
failureThreshold: 300
periodSeconds: 300
jenkinsUriPrefix: "/jenkins"
persistence:
enabled: true
existingClaim: jenkins-pvc
storageClass: gp2
size: 30Gi
accessMode: ReadWriteOnce
- jenkins-ingress와 맞추기 위해 url prefix를 설정하였고, 미리 만들어둔 PVC와 연동한다.
- 설치:
helm install jenkins jenkinsci/jenkins --kubeconfig="./kubeconfig.yaml" --namespace=jenkins -f jenkins-values.yaml
Jenkins Ingress 배포
- jenkins-ingress.yaml: 호스트가 "my_host"인 경우를 가정하였다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jenkins-ingress
namespace: tools
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: {acm-인증서-arn}
alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
spec:
rules:
- host: my_host
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ssl-redirect
port:
name: use-annotation
- path: /jenkins
pathType: Prefix
backend:
service:
name: jenkins
port:
number: 8080
- 설치:
kubectl apply -f jenkins-ingress.yaml --kubeconfig="./kubeconfig.yaml" --namespace=jenkins
배포가 완료되면, 다음 명령어를 통해 최초 admin 비밀번호를 가져온다.
kubectl exec --kubeconfig="./kubeconfig.yaml" --namespace=jenkins -it svc/jenkins -c jenkins -- /bin/cat /run/secrets/additional/chart-admin-password && echo
Ingress가 설정되었으므로, 다음 URL을 통해 접속 가능하다: {my_host}/jenkins
ArgoCD 배포
EBS 볼륨 생성
EC2 콘솔에서 ArgoCD Pod용으로 EBS 볼륨을 생성한다. ArgoCD Pod이 배포될 노드와 동일한 AZ(가용영역)의 볼륨만 접근 가능하므로 유의.
PV 생성
- argocd-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: argo-pv
spec:
capacity:
storage: 30Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: failure-domain.beta.kubernetes.io/zone
operator: In
values:
- ap-northeast-2c
awsElasticBlockStore:
volumeID: vol-1111111111111 // 위에서 생성한 EBS 볼륨의 ID를 넣는다.
fsType: ext4
- 생성:
--kubectl apply -f argocd-pv.yaml --kubeconfig="./kubeconfig.yaml" --namespace=jenkins
PVC 생성
- argocd-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: argo-pvc
namespace: argocd
spec:
storageClassName: ""
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 30Gi
- 생성:
kubectl apply -f argocd-pvc.yaml --kubeconfig="./kubeconfig.yaml" --namespace=jenkins
ArgoCD 배포
helm을 통해 배포한다. 다음 명령어를 통해 helm repo를 업데이트 해 둔다.
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
- ArgoCD는 배포시 총 7개의 Pod이 생성된다. (argocd-server, argocd-repo-server, argocd-redis, argocd-notification-controller, argocd-dex-server, argocd-application-controller, argocd-applicationset-controller)
- HTTPS 무한 리다이렉트 문제를 우회하기 위해 insecure 모드로 백엔드를 동작시킨다.
- nginx ingress의 url location과 매치시키기 위해 basehref 옵션으로 /argocd를 준다.
- argocd-values.yaml
global:
image:
tag: "v2.7.4"
server:
service:
type: LoadBalancer
insecure: true
basehref: /argocd
volumes:
- name: static-files
persistentVolumeClaim:
claimName: argo-server-pvc
configs:
credentialTemplates:
admin:
apiKey: enabled
- 설치:
helm install argocd argo/argo-cd --kubeconfig="./kubeconfig.yaml" --values argocd-values.yaml --namespace argocd
ArgoCD Ingress 배포
- argocd-ingress.yaml: 호스트가 "my_host"인 경우를 가정하였다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-ingress
namespace: tools
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: {acm-인증서-arn}
alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
spec:
rules:
- host: my_host
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ssl-redirect
port:
name: use-annotation
- path: /argocd
pathType: Prefix
backend:
service:
name: argocd-server
port:
name: http
- 설치:
kubectl apply -f argo-ingress.yaml --kubeconfig="./kubeconfig.yaml" --namespace=argocd
배포가 완료되면, 다음 명령어를 통해 최초 admin 비밀번호를 가져온다.
kubectl --kubeconfig="./kubeconfig.yaml" -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
Ingress가 설정되었으므로, 다음 URL을 통해 접속 가능하다: {my_host}/argocd
ArgoCD 정상작동이 안될 경우
- helm chart의 values.yaml이 제대로 적용이 안 될 경우, configMap을 직접 수정하도록 한다.
kubectl edit configmap argocd-cmd-params-cm -n argocd --kubeconfig="./kubeconfig.yaml"
data: 섹션의 다음 부분들을 수정하자.
data:
...
server.basehref: /argocd
server.insecure: "true"
server.rootpath: /argocd
...
- configMap을 수정하면, 관련 영향을 받는 Pod을 모두 재시작해야 한다.
- argocd 네임스페이스의 모든 deployments를 이용하여 rollout restart를 하자.
kubectl rollout restart deployments --namespace argocd --kubeconfig="./kubeconfig.yaml"
맺음말
이번 포스팅에서는 AWS EKS에 클러스터, 노드 그룹을 설정하고 Jenkins와 ArgoCD를 설치 후 Ingress까지 설정을 진행하였다.
이어지는 포스팅에서는 설치된 Jenkins와 ArgoCD의 서비스 내부 설정과 필수 팁, 토큰 세팅과 Git Action 설정, 그리고 실제 애플리케이션 배포를 수행해본다.