시스팀 구축 이후 운영하는 도중 발생하는 장애를 파악하고 이상이 발생했을 때 관리자에게 알리는 방법을 설명한다.
4.1.1 클러스터 상태 파악
EKS 컨트롤 플레인은 AWS를 통해 운영되는 관리형 서비스로 클러스터에 문제가 발생했는지 상태를 파악하는 정도로 충분히 활용할 수 있다.
4.1.2 Cloudwatch의 Container Insights로 애플리케이션 상태 파악
Cloudwatch의 Container Insights를 통해 애플리케이션 상태를 파악할 수 있는데 클러스터 노드, 파드, 네임스페이스, 서비스 레벨의 메트릭을 참조할 수 있다.
CloudWatch 에이전트를 데몬셋으로 동작시킨 후 필요한 메트릭을 CloudWatch로 전송하면 된다.
추가과정은
네임스페이스 생성
kubectl apply -f https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/master/k8s-yaml-templates/cloudwatch-namespace.yaml
namespace/amazon-cloudwatch created
CloudWatch용 서비스 계정 생성
kubectl apply -f https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/master/k8s-yaml-templates/cwagent-kubernetes-monitoring/cwagent-serviceaccount.yaml
serviceaccount/cloudwatch-agent created
clusterrole.rbac.authorization.k8s.io/cloudwatch-agent-role created
clusterrolebinding.rbac.authorization.k8s.io/cloudwatch-agent-role-binding created
모니터링: 메트릭 디렉터리를 설정하고 CloudWatch 사용자 메트릭을 생성해 그 메트릭의 경보를 생성한다.
시각화: CloudWatch의 Logs Insights를 사용해 대상 로그를 분석하고 CloudWatch의 대시보드로 시각화한다.
쿠버네티스는 재시작 등을 하게 되면 호스트가 변경 될 가능성이 있기 때문에 표준 출력으로 구성하는 것을 추천한다.
kubectl logs app=backend-app
으로 레이블을 지정해 특정 레이블에 있는 모든 파드의 로그를 참조할수도 있다.
4.2.2 로그 수집
AWS에서는 CloudWatch Logs라는 서비스를 제공하는데 이 책에서는 데몬셋으로 Fluentd 컨테이너를 동작시켜 파드 각각이 표준 출력한 로그를 확인하고 CloudWatch Logs로 수집, 관리하는 방법을 소개한다.
데이터 노드의 IAM 역할에 정책 추가
EKS에서 배포한 데몬셋에서 CloudWatch Logs에 로그를 전송하려면 데이터 플레인에 연결된 IAM 역할에 IAM 정책을 연결해야 한다.
플루언트디 컨테이너를 데몬셋으로 동작시키기
kubectl create configmap cluster-info --from-literal=cluster.name=eks-work-cluster --from-literal=logs.region=ap-northeast-2 -n amazon-cloudwatch
configmap/cluster-info created
플루언트디 컨테이너를 설치하는 매니페스트를 다운로드하고 플루언트디 컨테이너를 동작
플루언트디 컨테이너를 데몬셋으로 동작
kubectl apply -f fluentd.yaml
serviceaccount/fluentd created
clusterrole.rbac.authorization.k8s.io/fluentd-role created
clusterrolebinding.rbac.authorization.k8s.io/fluentd-role-binding created
configmap/fluentd-config created
daemonset.apps/fluentd-cloudwatch created
4.2.3 로그 저장
CloudWatch Logs로 전송된 로그는 기본적으로 기간 제한이 없어서 용도에 맞게 저장 기간을 설정해야한다.
CloudWatch Logs에서 보관 기간 설정
CloudWatch 로그 저장 기간은 로그 그룹 단위로만 설정 가능하므로 가장 오래 저장하고 싶은 로그에 맞춰 기간을 설정하는 것이 좋다.
4.2.4 로그 모니터링
CloudWatch 경보를 사용한 통지
애플리케이션에서 에러가 발생했을 때 통지를 보내고 싶다면 메트릭 필터에서 대상 로그를 추출하고 그것을 CloudWatch 메트릭으로 등록해 메트릭 경보를 생성하면 통지를 보낼 수 있다.
ERROR 로그가 출력된 경우 통지를 보내고 싶다면
메트릭 필터 조건을 'ERROR'로 설정하고 출력 횟수를 메트릭에 등록
출력 횟수 메트릭 통보 생성
메트릭 필터도 로그 그룹 단위로 설정해야 하기 때문에 앱이 A, B가 존재하는 경우 앱 A 에러, 앱 B 에러 등과 같이 별도로 필터와 경보를 생성하는 것이 좋다.
CloudWatch Logs 이벤트 검색을 이용한 디버그 예
CloudWatch Logs에서는 표준 출력이 JSON으로 저장된다.
# '애플리케이션 로그 AND 애플리케이션 이름이 backend-app AND "WARN"이나 "error"가 포함된다'라는 조건
{ $.log != "[*" && $.kubernetes.container_name = "backend-app" && ( $.log = "*WARN*" || $.log = "*error*" ) }
4.2.5 로그의 시각화와 분석
로그에서 에러를 추출하고 통지하는 것은 실시간 대응 속도를 위한 것이라면 시각화와 분석은 다양한 패턴을 파악해 장애를 바잊하거나 개발이 필요한 내용을 찾아내는 것이다.
CloudWatch는 저장된 로그를 검색하고 시각화하는 기능인 CloudWatch Logs Insights를 제공하며 여기서는 이 기능을 사용해 기본적인 로그 분석을 해본다.
위의 그림에서 View in Logs Insights를 클릭하면
# 애플리케이션 접속 확인
stats count(*) as ACCESS_COUNT
| filter ( kubernetes.container_name = "backend-app")
| filter ( log not like /^\[/ and (log like /Health GET API called./ or log like /
REGION GET ALL API/ or log like /LOCATION LIST BY REGION ID API/ )
# 어떤 애플리케이션에서 얼마만큼의 에러가 발생하고 있는지 확인
stats count(*) as ERROR_COUNT by kubernetes.container_name as APP
| filter ( log not like /^\[/ and (log like /WARN/ or log like /error/) )
쿼리 결과를 대시보드에 저장
Logs Insights 쿼리 결과는 CloudWatch 대시보드에 저장할 수 있는데 여러 쿼리 결과를 대시보드에 저장해두면 전체 상황을 파악할 수 있다.
위 사진의 Add to dashboard 버튼을 통해 시행 가능하다
4.2.6 리소스 삭제
curl 명령어로 다운로드한 디렉토리에서 실행한다.
IAM 역할에 정책 추가에서 생성한 IAM 정책을 삭제한다
데몬셋으로 자동 생성된 CloudWatch Logs의 로그 그룹 3개와 CloudWatch 대시보드도 삭제한다.
eks 클러스터 데이터 노드로 설정된 오토스케일링 그룹 이름을 매니페스트 문서에 입력한다.
결과
kubectl apply -f cluster-autoscaler.yaml
serviceaccount/cluster-autoscaler created
clusterrole.rbac.authorization.k8s.io/cluster-autoscaler created
role.rbac.authorization.k8s.io/cluster-autoscaler created
clusterrolebinding.rbac.authorization.k8s.io/cluster-autoscaler created
rolebinding.rbac.authorization.k8s.io/cluster-autoscaler created
deployment.apps/cluster-autoscaler created
기존에 설정한 v1.2.2는 베타 버전이 들어가 있어 cluster-autoscaler 버전을 업그레이드 해봤는데 이미지가 풀이 되지 않는 문제가 계속되고 있다.
Cluster Autoscaler에서 주의할 점
Cluster Autoscaler는 requests 값으로 스케줄링을 판단한다. 파드를 배치할 때만 스케일링을 판단하지 파드가 배치되면 노드의 스케줄링은 실행되지 않는다. 3.7절에서 이야기했던 대로 requests와 limits의 값을 적절히 조절해 리소스 사용량을 적절하게 맞춰야 한다.
AWS 오토스케일링 기능을 이용한 예방적 오토스케일링
문제가 발생했을 때 오토스케일링 하는 것이 아니라 임계값을 넘으면 노드를 추가하는 것이 운영 측면에선 더 좋다.
생성되는 Pod를 Pending 상태로 만들지 않으려면 노드의 부하를 판단할 필요가 있는데 이 때 CPU 사용률(pod_cpu_reserved_capacity)가 좋을 것이다. 이는 Container Insights를 활성화하면 자동 등록된다.
4.3.2 Horizontal Pod Autoscaler를 이용한 파드 오토스케일링
노드 오토스케일링은 파드를 원하는 수만큼 동작시키기 위해 용량을 확보하는 기능이므로 애플리케이션 스케일링을 하려면 쿠버네티스의 Horizontal Pod Autoscaler 기능을 사용해 파드 리소스 사용률에 맞춰 파드의 스케일 아웃, 인 기능을 구현할 수 있다.
HPA로 파드 수를 자동 조절하려면 클러스터 내부에서 파드 리소스를 파악하고 있어야 한다. HPA에서는 메트릭 서버슬 사용해 사용 현황을 파악한다.
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created
kubectl get deployments metrics-server -n kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
metrics-server 1/1 1 1 63s
kubectl apply -f horizontal-pod-autoscaler.yaml
horizontalpodautoscaler.autoscaling/backend-app created
부하를 준다.
kubectl run -i --tty load-generator --imgae=busybox --rm -- sh
while true; do wget -q -0- http://backend-app-service.eks-work.svc.cluster.local:8080/health; done
HPA에서는 기본값으로 스케일 아웃은 30초, 스케일 인은 5분에 한 번 동작한다.
4.4 보안
최소한의 보안 지식에 대해 설명한다.
4.4.1 클러스터 보안
인증
쿠버네티스 클러스터를 조작할 떄는 kubectl을 사용하며 이는 EKS에서도 동일하다. AWS CLI의 aws eks update-kubeconfig를 실행하면 kubectl을 자동으로 설정할 수 있다.
aws eks update-kubeconfig --name eks-work-cluster
Added new context arn:aws:eks:ap-northeast-2:566620743708:cluster/eks-work-cluster to /Users/hojin/.kube/config
이 설정으로 kubectl이 AWS CLI 인증 탐색 구조를 이용해 EKS 인증이 가능해진다.
kubectl은 인증 설정에서 AWS CLI를 사용하도록 설정하면 EKS 인증 시 해당 도구의 aws eks get-token 명령을 사용해 토큰을 생성하고 그것을 클러스터 인증 문자열로 사용한다. 토큰은 Go용 AWS SDK의 sts.GetCallerIdentityRequest에서 생성된 URL을 base64 인코딩하고 k8s-aws-v1 접두사를 붙인 문자열이다.
kubectl은 이 문자열을 사용해 EKS의 컨트롤 플레인에 요청을 보낸다. EKS는 받은 토큰을 디코딩하고 sts.GetCallerIdentity 요청을 발행해 응답에 포함된 IAM 사용자나 IAM 역할의 ARN을 확인한다.
인가
kubectl로 접속한 요청이 정상적으로 인증된 후에는 요청한 조작이 허가를 받은지 점검할 필요가 있다. 쿠버네티스에서는 Role-Based Access Control 기능을 이용해 구현한다. Role, RoleBinding이라는 쿠버네티스 오브젝트를 사용해 설정한다. 롤은 어떤 조작을 허가할지 권한 셋을 정의하고 롤바인딩은 어떤 사용자에게 어떤 롤을 부여할지 정의한다.
AWS는 aws-auth 컨피그맵 안에 AWS CLI 인증 정보로 설정한 IAM 사용자, IAM 역할의 아마존 리소스 이름, 쿠버네티스 오브젝트로 그룹을 연결해놓고 롤바인딩으로 그 그룹과 롤을 연결시키는 것으로 구현한다.
이런 방법을 통해 각 관리자가 특정 리소스에만 접근 가능하도록 권한을 부여할 수 있다.
애플리케이션 개발자로 리소스 접속이 제한된 사용자를 예를 들어서 특정 네임스페이스의 파드와 파드 로그, 디플로이먼트와 레플리카셋, 서비스만 참조 가능한 사용자를 등록해보자.
롤과 롤 바인딩 등록
kubectl apply -f security/rbac.yaml
namespace/rbac-test-ns created
clusterrole.rbac.authorization.k8s.io/rbac-test-role created
rolebinding.rbac.authorization.k8s.io/rbac-test-role-binding created
컨피그맵의 aws-auth에 롤바인딩의 .subjects[].name에서 설정한 이름과 IAM 사용자 또는 IAM 역할의 ARN 페어를 설정
eksctl create iamidentitymapping --region ap-northeast-2 --cluster eks-work-cluster --username rbacTest --group rbac-test-group --arn arn:aws:iam::566620743708:user/developer
checking arn arn:aws:iam::566620743708:user/developer against entries in the auth ConfigMap
adding identity "arn:aws:iam::566620743708:user/developer" to auth ConfigMap
AWS CLI로 사용할 인증 정보를 신규로 생성한 IAM 사용자의 정보로 변경하고 kubectl 관련 명령을 실행하면 rbac-test-ns 외의 조작은 불가능하다.
사용자 정보 변경 후 기존 네임스페이스로 변경
aws configure
AWS Access Key ID [****************2EC6]: AKIAYH3J2NAOMZZTHPXT
AWS Secret Access Key [****************v2bI]:
Default region name [ap-northeast-2]:
Default output format [json]:
kubectl config use-context eks-work
Switched to context "eks-work".
기존 네임스페이스 리소스 접근 시도
kubectl get pod,deployments,replicaset,service
Error from server (Forbidden): pods is forbidden: User "rbacTest" cannot list resource "pods" in API group "" in the namespace "eks-work"
Error from server (Forbidden): deployments.apps is forbidden: User "rbacTest" cannot list resource "deployments" in API group "apps" in the namespace "eks-work"
Error from server (Forbidden): replicasets.apps is forbidden: User "rbacTest" cannot list resource "replicasets" in API group "apps" in the namespace "eks-work"
Error from server (Forbidden): services is forbidden: User "rbacTest" cannot list resource "services" in API group "" in the namespace "eks-work"
rbac-test-ns 네임스페이스 리소스 참조
kubectl get pod,deployments,replicaset,service -n rbac-test-ns
No resources found in rbac-test-ns namespace.
리소스 생성 실패
kubectl create deployment tnginx-test --image=nginx:latest
error: failed to create deployment: deployments.apps is forbidden: User "rbacTest" cannot create resource "deployments" in API group "apps" in the namespace "eks-work"
rbac-test-ns에도 추가 불가
kubectl create deployment tnginx-test --image=nginx:latest -n rbac-test-ns
error: failed to create deployment: deployments.apps is forbidden: User "rbacTest" cannot create resource "deployments" in API group "apps" in the namespace "rbac-test-ns"
4.4.2 노드 보안
파드에 부여하는 호스트 권한 제어
클러스터를 관리하면서 어떤 파드든 무조건 생성해도 되는 것은 아니다. 쿠버네티스는 파드에 부여할 권한을 관리하는 파드 시큐리티 폴리시라는 기능을 가지고 있다. 현재 EKS 클러스터에는 아무 제한이 걸려있지 않다. 파드를 특권 컨테이너로 이동시켜 노드 쪽 권한을 사용할 수도 있다.
kubectl get namespace eks-work --show-labels
NAME STATUS AGE LABELS
eks-work Active 7d4h kubernetes.io/metadata.name=eks-work
kubectl apply -f -<<EOF
heredoc> apiVersion: v1
heredoc>
heredoc> kind: Pod
heredoc> metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:latest
heredoc> EOF
Error from server (Forbidden): error when creating "STDIN": pods "nginx" is forbidden: violates PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
여기서 nginx 컨테이너를 실행하려하면 권한에 막힌다.
과거에는 Pod Security Policy로 제어했지만 지금 해당 feature는 Deprecated되었고, Pod Security Admission 혹은 3rd party plugin으로 대체할 수 있다.
과거의 파드 시큐리티 폴리시를 사용해 파드에서 사용할 수 있는 권한을 제어함으로써 노드를 보호할 수 있었다. 만약 특정 사용자에게만 권한제한을 적용하고 싶을 경우 파드 시큐리티 폴리시를 생성해 해당 파드 시큐리티 폴리시를 룰로 선언하고 롤바인딩으로 롤과 사용자를 매핑시키면 파드 시큐리티 폴리시를 적용할 수 있었다.
4.4.3 파드 보안
컨테이너 라이프사이클에 맞춘 보안 대책의 필요성
컨테이너 보안은 라이프사이클에 맞춰 수행하며 컨테이너 이미지를 생성할 때는 취약성을 점검하고 동작시킬 때는 동작을 감시한다.
컨테이너 이미지 취약성: 컨테이너 이미지를 스캔해 문제가 있는 소프트웨어 버전이 포함되었는지 자동으로 확인하게함으로써 취약한 컨테이너가 배포되는 것을 방지한다.
동작 감시(런타임 보호): 실제 컨테이너에 문제되는 동작이 실행되는지 확인한다.
4.4.4 네트워크 보안
네트워크 수준의 보안은 인그레스와 내부통신 두 가지 관점으로 생각할 수 있다.
인그레스는 AWS 기능을 이용, 내부통신은 쿠버네티스 기능을 사용해 대응한다.
엔드포인트 IP 주소 제한
EKS에서는 엔드포인트의 IP 주소 제한 기능이 있으며 콘솔에서 설정할 수 있다.
kubectl을 VPC 내부로 제한하는 프라이빗 엔드포인트
기본적으로 EKS 클러스터 엔드포인트는 인터넷에 공개되는데 IAM으로 인증하고 앞에서 설명한 IP 주소로 접속도 제한할 수 있다.
프라이빗 엔드포인트도 제공해 VPC 내부에서만 접속할 수 있는 클러스터를 생성할 수도 있다.
네트워크 정책을 사용한 클러스터 내부 통신 제어
파드에는 VPC 내부의 IP 주소가 할당돼 클러스터 외부와 통신할 수 있지만 어떤 노드에 어떤 IP 주소가 할당되는지 제어할 수 없다. 또, 모든 노드에 공통 보안 그룹이 설정돼있어 클러스터 내부 통신을 보안 그룹으로 제어하는 것은 어렵다.
클러스터 내부 서비스 사이의 통신 제어는 네트워크 정책이라는 클러스터 내부 구조를 이용해 실현한다.
매니페스트를 통해 살펴보면 대상 네임스페이스 내에서는 모든 서비스 사이의 통신을 거부한다.
cat network-policy-all-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
spec:
podSelector:
# 모든 파드(Pod)에 적용
matchLabels: {}
이후 서비스 B에서 http 요청만 허가하는 네트워크 정책을 생성한다.
.spec.podSelector.matchLabels에서 어떤 파드에 네트워크 정책을 적용할지 설정한다.
여기선 app: serviceA에 .spec.ingress에는 전달받는 통신의 허가 조건을 설정한다. 이렇게 두 설정으로 그림과 같은 구성을 실현할 수 있다.
cat network-policy-allow-http-from-serviceB2ServiceA.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
# 적용 대상 네임스페이스 설정
name: network-policy-for-servicea
spec:
podSelector:
matchLabels:
# 적용 대상 파드 레이블 설정
app: ServiceA
policyTypes:
- Ingress
ingress:
# 수신 규칙 설정
- from:
- podSelector:
matchLabels:
app: ServiceB
ports:
- protocol: TCP
port: 80
kubectl apply -f network-policy-all-deny.yaml
networkpolicy.networking.k8s.io/default-deny created
kubectl apply -f network-policy-allow-http-from-serviceB2ServiceA.yaml
networkpolicy.networking.k8s.io/network-policy-for-servicea created
kubectl apply -f network-policy-sample-serviceA.yaml
deployment.apps/servicea created
service/servicea created
calico
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico-vxlan.yaml
poddisruptionbudget.policy/calico-kube-controllers created
serviceaccount/calico-kube-controllers created
serviceaccount/calico-node created
serviceaccount/calico-cni-plugin created
configmap/calico-config created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgpfilters.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/caliconodestatuses.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipreservations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/kubecontrollersconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org created
clusterrole.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrole.rbac.authorization.k8s.io/calico-node created
clusterrole.rbac.authorization.k8s.io/calico-cni-plugin created
clusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrolebinding.rbac.authorization.k8s.io/calico-node created
clusterrolebinding.rbac.authorization.k8s.io/calico-cni-plugin created
daemonset.apps/calico-node created
deployment.apps/calico-kube-controllers created
서비스 A로 HTTP 요청
kubectl run -it servicec --image=busybox --labels="app=ServiceC" --rm -- sh
wget -q -0 - http://servicea.eks-work.svc.cluster.local
4.5 매니페스트 관리와 지속적 통합/지속적 전달
서비스 환경에서 유지, 운영하기 위해서는 항상 클러스터의 상태를 감시해 문제가 발생할 경우 통지 받는 구조로 만들어야 한다.
4.5.1 깃옵스와 깃옵스를 구현하기 위한 도구 등장
쿠버네티스는 모든 설정 정보를 매니페스트라는 yaml 파일로 정의한다 모든 설정을 코드로 관리하면 CI/CD 구현이 가능하다.
CD 툴로는 다양한 툴들이 존재한다.
4.5.2 CodePipeline을 이용해 깃옵스 구현
AWS에서도 CodePipeline이라는 CI/CD 서비스를 제공하는데 소스 작성, 빌드, 배포 단계를 정의하고 각종 설정을 수행해 CI/CD 파이프라인을 구성할 수 있는 서비스다.
코드를 리포지터리에 푸시
파이프라인이 동작
애플리케이션 빌드를 실시
컨테이너 이미지를 생성해 ECR에 푸시
ECR 컨테이너 이미지를 EKS에 자동으로 배포
소스 리포지터리와 EKS 매니페스트 리포지터리는 별도 관리되어야 하고 빌드 단계와 배포 단계도 분리해 실행해야 하지만 여기서는 간소화된 파이프라인을 구축한다
public class RegionDto {
private Integer regionId;
private String regionName;
public RegionDto() {
}
public RegionDto(Region region) {
this.regionId = region.getRegionId();
this.regionName = "*" + region.getRegionName();
}
}
cat backend-app/build.gradle
plugins {
id 'org.springframework.boot' version '2.2.5.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'k8sbook'
version = '1.0.1'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
ECR의 URI와 버전 번호를 업데이트, 매니페스트 수정
ECR에 새 도커 이미지를 푸시하고, EKS 클러스터에 매니페스트 적용, 새로운 도커 이미지를 ECR에서 풀해서 배포한다.
우선 kustomization.yaml의 이미지 태그와 ECR URI를 변경한다.
cat cicd/kustomization/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../base # 기반이 되는 매니페스트 장소
images:
- name: backend-app-image # 여기는 바꾸지 않음
newTag: 1.0.1 # 애플리케이션 버전 번호
newName: 566620743708.dkr.ecr.ap-northeast-2.amazonaws.com/k8sbook/backend-app # ECR 레지스트리 URI
코드 커밋에 푸시
리포지터리에 푸시한다.
git add .
git commit -m 'Test'
git push origin master
Username for 'https://git-codecommit.ap-northeast-2.amazonaws.com': eks-app-git-at-566620743708
Password for 'https://eks-app-git-at-566620743708@git-codecommit.ap-northeast-2.amazonaws.com':
오브젝트 나열하는 중: 469, 완료.
오브젝트 개수 세는 중: 100% (469/469), 완료.
Delta compression using up to 11 threads
오브젝트 압축하는 중: 100% (261/261), 완료.
오브젝트 쓰는 중: 100% (469/469), 2.08 MiB | 730.00 KiB/s, 완료.
Total 469 (delta 153), reused 437 (delta 139), pack-reused 0
remote: Validating objects: 100%
To https://git-codecommit.ap-northeast-2.amazonaws.com/v1/repos/eks-work-cicd-repo
* [new branch] master -> master
자동으로 EKS에 배포된 것 확인
[Container] 2024/06/15 01:38:26.869089 Running command python get-pip.py
ERROR: This script does not work on Python 3.7 The minimum supported Python version is 3.8. Please use https://bootstrap.pypa.io/pip/3.7/get-pip.py instead.
[Container] 2024/06/15 01:38:26.993854 Command did not exit successfully python get-pip.py exit status 1
[Container] 2024/06/15 01:38:26.997881 Phase complete: PRE_BUILD State: FAILED
[Container] 2024/06/15 01:38:26.997900 Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: python get-pip.py. Reason: exit status 1
환경에 따라 다른 정보들을 동적으로 변경시키기 위해서 kustomize는 동적으로 값을 변경하고 싶다는 요구사항을 해결하는 구조를 제공한다.
kustomize는 kubectl의 서브 명령어로 여러 개의 매니페스트나 환경에 의존적인 설정값을 합해 매니페스트 하나로 만들어 준다.
디렉토리 각각의 kustomization.yaml에는 기본 매니페스트가 어디에 있는지 기본 설정을 작성한다. 이렇게 하면 test 디렉토리의 kustomization.yaml와 deployment.yaml은 기본 파일을 덮어쓰며 생성할 리소스는 -test 접미사를 붙인다.
cat cicd/kustomization/test/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../base # 기본이 되는 매니페스트 디렉토리
patches:
- deployment.yaml # deployment.yaml의 내용을 덮어쓰기하는 설정
nameSuffix: -test # 모든 리소스 이름 끝에 -test를 붙인다
commonLabels: # 모든 리소스에 설정하는 레이블
app: backend-app-test
images:
- name: centos
newTag: # 애플리케이션 버전 번호
newName: # ECR 레지스트리 URI
그리고 덮어쓰기하는 deployment.yaml에는 덮어쓰기 대상의 디플로이먼트 이름을 설정하고 레플리카셋 수만 적어둔다.
cat cicd/kustomization/test/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-app # 덮어쓰기 대상 리소스 이름
spec:
replicas: 1 # 이 부분만 base 매니페스트 덮어쓰기
이 구조를 파이프라인에 도입하면 환경에 따라 변경되는 값들을 동적으로 변경하는 파이프라인을 구성할 수 있다.
4.6 버전 관리
쿠버네티스 버전 업데이트에 필요한 작업과 방법, 고려사항을 설명한다.
4.6.1 쿠버네티스 버전 업데이트 계획과 지원 정책
쿠버네티스는 연간 4번 새로운 버전이 출시되며 EKS도 최신버전을 포함해 3개의 버전을 서포트한다. 하나의 버전의 수명은 9개월 정도라고 보면 된다.
4.6.2 버전 업데이트에 필요한 작업
쿠버네티스는 컨트롤 플레인과 데이터 플레인을 각각 업데이트 해야 되는데 EKS에서 eksctl 관련 명령을 사용하면 효율적으로 실행할 수 있다.
eksctl upgrade cluster --name eks-work-cluster --approve
2024-06-16 20:34:34 [ℹ] will upgrade cluster "eks-work-cluster" control plane from current version "1.29" to "1.30"
2024-06-16 20:43:10 [✔] cluster "eks-work-cluster" control plane has been upgraded to version "1.30"
2024-06-16 20:43:10 [ℹ] you will need to follow the upgrade procedure for all of nodegroups and add-ons
2024-06-16 20:43:10 [ℹ] re-building cluster stack "eksctl-eks-work-cluster-cluster"
2024-06-16 20:43:10 [✔] all resources in cluster stack "eksctl-eks-work-cluster-cluster" are up-to-date
2024-06-16 20:43:10 [ℹ] checking security group configuration for all nodegroups
2024-06-16 20:43:10 [ℹ] all nodegroups have up-to-date cloudformation templates
데이터플레인에서 동작하는 시스템 컴포넌트 파드인 kube-proxy와 coredns를 업데이트한다.
데이터플레인은 실제 서비스가 동작하고 있기 때문에 주의해야 한다. 새로운 노드 그룹으로 새로운 버전의 데이터 플레인을 생성하고 기존 데이터 플레인에서 전환하는 방법을 사용할 수 있다.
# 현재 노드 그룹 설정 확인
eksctl get nodegroups --cluster=eks-work-cluster
eksctl create nodegroup --cluster eks-work-cluster --version 1.29 --name eks-work-nogroup-2 --node-type t2.small --nodes 2 --nodes-min 2 --nodes-max 5 --node-ami auto
모든 노드가 Ready상태가 되면 다음 명령으로 기존 노드 그룹을 삭제한다. 기존 노드 그룹을 삭제하면 파드가 자동으로 새로운 노드에 재배치된다.