AEWS) EKS Automation

CloudNet@팀에서 진행하는 AWS EKS Workshop 실습 스터디 참가글입니다.

AWS EKS Workshop을 기반으로, AWS EKS 배포 및 실습하고, 내용을 공유합니다.


1. Automation Overview

DevOps는 소프트웨어 개발과 IT 운영의 결합입니다. DevOps는 제품 개발 속도를 높이고 수명 주기 전반에 걸쳐 효율성을 촉진하기 위한 일련의 도구와 방법론입니다. DevOps는 개발 및 배포 프로세스를 괴롭히는 단조롭고 반복적인 작업을 관리하고 자동화하여 애플리케이션과 업데이트의 제공 속도를 높일 수 있습니다.

DevOps is the combination of software development and IT operations. It is a set of tools and methodologies designed to speed up the development of a product and facilitate efficiency throughout its lifecycle. DevOps can increase the rate at which applications and updates are delivered by managing and automating the monotonous and repetitive tasks that plague the development and deployment process.

How automation drives DevOps, CircleCI blog

소프트웨어 개발 및 배포 과정에서, 크게 변경이 필요한 일들은 자주 일어나지 않는다. 변경이 필요한 부분에 대한 작업을 처리/결정되고 나면, 남은 일들은 단순하고 반복적이며, 기다리고, 실수할 여지가 있는 일들 뿐이다.

가령 신규 서비스를 위한 API를 개발하는 과정이라면, 개발 도중 테스트 / 마무리 후에는 도커파일로 이미지를 말고, 레지스트리에 올리며, 태그에 맞게 컨테이너의 이미지들이 교체되기를 기다려야할 것이다. 배포될 서버는 t4.large의 유형을 사용하며, 10대가 사용될 것이라고 결정되면 나머지 시간은 10대가 뜨기를 기다리며, 완료되면 앞단에 로드밸런서를 붙이고, 보안그룹에 오타를 내거나 필요한 태그를 누락할 일만 남았을 것이다.

자동화의 도입은 바로 이러한 부분들을 해결할 수 있다. 자동화는 지루하고 반복적인 수동작업에 대한 부담/비용을 줄일 수 있고, 수동으로 할 경우의 대기시간과, 실수할 여지를 차단한다.

이번 장에서는 EKS 운영 시, 인프라 프로비저닝과 코드 배포등에 대해서 함께 사용할 수 있는 몇몇 도구에 대한 실습을 진행할 것이다. 이 워크샵에서는 연관 인프라 프로비저닝에 대해서는 AWS Controllers for Kubernetes (ACK)와 Crossplane이 소개되었고, 코드 배포(컨테이너 배포)에 있어서는 Flux와 ArgoCD가 소개 되었다. 이들 중 ACK와 Flux에 대한 소개와 실습을 진행하고자 한다.

실습환경 배포

  • 실습 환경 배포
# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick6.yaml

# CloudFormation 스택 배포
예시) aws cloudformation deploy --template-file eks-oneclick6.yaml --stack-name myeks --parameter-overrides KeyName=nasirk17 SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=AKIA5... MyIamUserSecretAccessKey='CVNa2...' ClusterBaseName=myeks --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text

# 작업용 EC2 SSH 접속
ssh -i ~/.ssh/nasirk17 .pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
  • 이후 ExternalDNS, LB Controller, Monitoring(Prometheus/Grafana)배포

추가 구성요소는 지난글들 과 유사하므로 해당 글 참조,


2. AWS Controller for Kubernetes (ACK)

Kubernetes용 AWS 컨트롤러(ACK)를 사용하면 Kubernetes에서 직접 AWS 서비스 리소스를 정의하고 사용할 수 있습니다. ACK를 사용하면 클러스터 외부에서 리소스를 정의하거나, 데이터베이스 또는 메시지 큐와 같이 클러스터 내의 지원 기능을 제공하는 서비스를 실행할 필요 없이 Kubernetes 애플리케이션을 위한 AWS 관리형 서비스를 활용할 수 있습니다.

ACK는 AWS의 지원을 받는 오픈 소스 프로젝트입니다. 이 프로젝트는 공통 런타임, 코드 생성기, 공통 테스트 도구, 개별 AWS 서비스 API를 위한 쿠버네티스 사용자 정의 컨트롤러를 포함하는 여러 소스 코드 리포지토리로 구성되어 있습니다.

AWS Controllers for Kubernetes (ACK) lets you define and use AWS service resources directly from Kubernetes. With ACK, you can take advantage of AWS managed services for your Kubernetes applications without needing to define resources outside of the cluster or run services that provide supporting capabilities like databases or message queues within the cluster.

ACK is an open source project built with ❤️ by AWS. The project is composed of many source code repositories containing a common runtime, a code generator, common testing tools and Kubernetes custom controllers for individual AWS service APIs.

AWS Controllers for Kubernetes (ACK), Github

ACK는 k8s 내에서 AWS 리소스를 직접 정의하고 사용할 수 있는 오픈소스도구이다. EKS 는 AWS 환경에서 운영되므로, RDS(DB)/S3(Storage)/VPC(Network)등과 같은 다른 AWS서비스와 연계하여 시스템 안정성을 향상 시킬 수 있다.

이미 지난 실습들을 통해서, ELB(AWS Loadbalancer controller)/EBS,EFS(CSI Driver)/Route53(ExternalDNS) 등을 통해 외부 AWS 리소스와 연동을 자동화하였다. 같은맥락에서, 더 다양한 서비스와 연동을 제공해주는 도구라고 생각된다.

  • 예시) 사용자는 kubectl (쿠버네티스 api) 를 사용하여 AWS S3 버킷 생성
  • 예시) 쿠버네티스 api 는 ack-s3-controller 에 요청을 전달하고, ack-s3-controller(IRSA)이 AWS S3 API 를 통해 버킷을 생성


ACK에서 지원하는 AWS 서비스

230611 현재 17개 서비스 GA General Availability (정식 출시) / 10개 Preview (평가판)

  • Maintenance Phases 관리 단계
    • GENERAL AVAILABILITY (상용 서비스 권장)
    • PREVIEW (테스트 단계, 상용 서비스 비권장)
    • Deprecated
    • Not Supported
  • GA 서비스 : ApiGatewayV2, CloudTrail, DynamoDB, EC2, ECR, EKS, IAM, KMS, Lambda, MemoryDB, RDS, S3, SageMaker… 등 17개 서비스
  • Preview 서비스 : ACM, ElastiCache, EventBridge, MQ, Route 53, SNS, SQS… 등 10개 서비스


권한

k8s api 와 aws api 의 2개의 RBAC 시스템 확인, 각 서비스 컨트롤러 파드는 AWS 서비스 권한 필요 ← IRSA role for ACK Service Controller

클러스터 외부 인프라의 프로비저닝 관리라면 테라폼등의 IaC도구등을 활용하는방법도 있을것이다. 그럼에도 ACK를 사용해야한다면 어떤 이유들이 있을까는 생각이 들었었다.

테라폼을 사용한다면, 그만큼의 관리포인트/시스템 복잡성이 늘어날것이다. 기본적인 테라폼에 대한 러닝커브 뿐만 아니라, 테라폼의 구성 파일들(*.tf)이나 backend는 어떻게 관리 할 것인가, 외부 인프라의 변경이 필요할 경우 절차/파이프라인에는 어떻게 반영할 수 있을까, 보안이 필요한 데이터베이스 암호등과 같은 기밀 정보 관리 방법 등도 고려해야 할것이다.

하지만 ACK를 사용해서 클러스터 외부 인프라를 관리한다면, 관리 범위를 클러스터내에서로 한정시킬 수 있다는 장점이 있다.

ACK를 사용하면 클러스터 외부에서 리소스를 정의할 필요 없이 Kubernetes 애플리케이션을 위한 AWS 관리형 서비스를 활용할 수 있습니다. 따라서 애플리케이션의 종속성을 관리하기 위한 전반적인 복잡성이 줄어듭니다.

With ACK, you can take advantage of AWS managed services for your Kubernetes applications without having to define resources outside of the cluster. This reduces the overall complexity for managing the dependencies of your application.

AWS Controllers for Kubernetes (ACK), EKS Workshop

(테라폼을 통한) 클러스터 외부에서 리소스를 정의할 필요 없이 k8s 클러스터 내부에서 필요한 관리형 서비스를 활용할 수 있고, 이미 어플리케이션등에서 사용될 secret과 configmap 같은 k8s 리소스를 외부 인프라를 구성하는데 재사용할 수 있을것이다.

또한 테라폼에서의 단점을 꼽자면, 명령어를 통해 실행되기 때문에 생성된 외부 인프라에 대하여 콘솔이나 CLI를 통한 변경이 발생해 Sync가 깨졌을때(=Drift) 자동화된 대응이 불가능한 점이라고 생각한다. ACK는 Recovering from Drif 문서에서 설명하듯, ACK 컨트롤러가 Default 10시간마다 체크해서 동기화를 맞춰준다. 또한 terraform import 명령어처럼, 다른방식으로 생성된 리소스를 ACK의 관리범위내로 당겨올수도(Adopting Existing AWS Resources) 있다. 이러한 관점에서 접근한다면 나쁜 선택지는 아닌듯하다.

2.1. S3 실습

ACK S3 Controller 설치 with Helm

# 서비스명 변수 지정
export SERVICE=s3

# helm 차트 다운로드
#aws ecr-public get-login-password --region us-east-1 | helm registry login --username AWS --password-stdin public.ecr.aws
export RELEASE_VERSION=$(curl -sL https://api.github.com/repos/aws-controllers-k8s/$SERVICE-controller/releases/latest | grep '"tag_name":' | cut -d'"' -f4 | cut -c 2-)
helm pull oci://public.ecr.aws/aws-controllers-k8s/$SERVICE-chart --version=$RELEASE_VERSION
tar xzvf $SERVICE-chart-$RELEASE_VERSION.tgz

# helm chart 확인
tree ~/$SERVICE-chart

# ACK S3 Controller 설치
export ACK_SYSTEM_NAMESPACE=ack-system
export AWS_REGION=ap-northeast-2
helm install --create-namespace -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller --set aws.region="$AWS_REGION" ~/$SERVICE-chart

# 설치 확인
helm list --namespace $ACK_SYSTEM_NAMESPACE
kubectl -n ack-system get pods
kubectl get crd | grep $SERVICE
buckets.s3.services.k8s.aws                  2023-06-10T16:35:32Z

kubectl get all -n ack-system
kubectl get-all -n ack-system
kubectl describe sa -n ack-system ack-s3-controller

IRSA 설정

# Create an iamserviceaccount - AWS IAM role bound to a Kubernetes service account
eksctl create iamserviceaccount \
  --name ack-$SERVICE-controller \
  --namespace ack-system \
  --cluster $CLUSTER_NAME \
  --attach-policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`AmazonS3FullAccess`].Arn' --output text) \
  --override-existing-serviceaccounts --approve

# 확인 >> 웹 관리 콘솔에서 CloudFormation Stack >> IAM Role 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME

# Inspecting the newly created Kubernetes Service Account, we can see the role we want it to assume in our pod.
kubectl get sa -n ack-system
kubectl describe sa ack-$SERVICE-controller -n ack-system

# Restart ACK service controller deployment using the following commands.
kubectl -n ack-system rollout restart deploy ack-$SERVICE-controller-$SERVICE-chart

# IRSA 적용으로 Env, Volume 추가 확인
kubectl describe pod -n ack-system -l k8s-app=$SERVICE-chart
...

S3 버킷 생성, 업데이트, 삭제

  • S3 버킷 생성, 업데이트, 삭제
# [터미널1] 모니터링
watch -d aws s3 ls

# S3 버킷 생성을 위한 설정 파일 생성
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
export BUCKET_NAME=my-ack-s3-bucket-$AWS_ACCOUNT_ID

read -r -d '' BUCKET_MANIFEST <<EOF
apiVersion: s3.services.k8s.aws/v1alpha1
kind: Bucket
metadata:
  name: $BUCKET_NAME
spec:
  name: $BUCKET_NAME
EOF

echo "${BUCKET_MANIFEST}" > bucket.yaml
cat bucket.yaml | yh

# S3 버킷 생성
aws s3 ls
kubectl create -f bucket.yaml
# bucket.s3.services.k8s.aws/my-ack-s3-bucket-<my account id> created

# S3 버킷 확인
aws s3 ls
kubectl get buckets
kubectl describe bucket/$BUCKET_NAME | head -6
# Name:         my-ack-s3-bucket-596152156334
# Namespace:    default
# Labels:       <none>
# Annotations:  <none>
# API Version:  s3.services.k8s.aws/v1alpha1
# Kind:         Bucket

aws s3 ls | grep $BUCKET_NAME
# 2023-06-11 01:42:18 my-ack-s3-bucket-596152156334

# S3 버킷 업데이트 : 태그 정보 입력
read -r -d '' BUCKET_MANIFEST <<EOF
apiVersion: s3.services.k8s.aws/v1alpha1
kind: Bucket
metadata:
  name: $BUCKET_NAME
spec:
  name: $BUCKET_NAME
  tagging:
    tagSet:
    - key: myTagKey
      value: myTagValue
EOF

echo "${BUCKET_MANIFEST}" > bucket.yaml

# S3 버킷 설정 업데이트 실행 : 필요 주석 자동 업뎃 내용이니 무시해도됨!
kubectl apply -f bucket.yaml

# S3 버킷 업데이트 확인 
kubectl describe bucket/$BUCKET_NAME | grep Spec: -A5
Spec:
  Name:  my-ack-s3-bucket-596152156334
  Tagging:
    Tag Set:
      Key:    myTagKey
      Value:  myTagValue

# S3 버킷 삭제
kubectl delete -f bucket.yaml

# verify the bucket no longer exists
kubectl get bucket/$BUCKET_NAME
aws s3 ls | grep $BUCKET_NAME
  • ACK S3 Controller 삭제
# helm uninstall
export SERVICE=s3
helm uninstall -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller

# ACK S3 Controller 관련 crd 삭제
kubectl delete -f ~/$SERVICE-chart/crds

# IRSA 삭제
eksctl delete iamserviceaccount --cluster myeks --name ack-$SERVICE-controller --namespace ack-system
  • 실습 결과
  • 터미널에서 조회한 ACK S3 컨트롤러 배포 결과 및 CRD 조회
  • 생성된 IAM Role 및 권한 확인
  • S3 버킷 생성 및 업데이트 확인

2.2. EC2 & VPC 실습

ACK EC2-Controller 설치 with Helm

# 서비스명 변수 지정 및 helm 차트 다운로드
export SERVICE=ec2
export RELEASE_VERSION=$(curl -sL https://api.github.com/repos/aws-controllers-k8s/$SERVICE-controller/releases/latest | grep '"tag_name":' | cut -d'"' -f4 | cut -c 2-)
helm pull oci://public.ecr.aws/aws-controllers-k8s/$SERVICE-chart --version=$RELEASE_VERSION
tar xzvf $SERVICE-chart-$RELEASE_VERSION.tgz

# helm chart 확인
tree ~/$SERVICE-chart

# ACK EC2-Controller 설치
export ACK_SYSTEM_NAMESPACE=ack-system
export AWS_REGION=ap-northeast-2
helm install -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller --set aws.region="$AWS_REGION" ~/$SERVICE-chart

# 설치 확인
helm list --namespace $ACK_SYSTEM_NAMESPACE
kubectl -n $ACK_SYSTEM_NAMESPACE get pods -l "app.kubernetes.io/instance=ack-$SERVICE-controller"
kubectl get crd | grep $SERVICE
# dhcpoptions.ec2.services.k8s.aws             2023-06-10T16:54:15Z
# elasticipaddresses.ec2.services.k8s.aws      2023-06-10T16:54:15Z
# instances.ec2.services.k8s.aws               2023-06-10T16:54:15Z
# internetgateways.ec2.services.k8s.aws        2023-06-10T16:54:15Z
# natgateways.ec2.services.k8s.aws             2023-06-10T16:54:15Z
# routetables.ec2.services.k8s.aws             2023-06-10T16:54:15Z
# securitygroups.ec2.services.k8s.aws          2023-06-10T16:54:15Z
# subnets.ec2.services.k8s.aws                 2023-06-10T16:54:15Z
# transitgateways.ec2.services.k8s.aws         2023-06-10T16:54:15Z
# vpcendpoints.ec2.services.k8s.aws            2023-06-10T16:54:15Z
# vpcs.ec2.services.k8s.aws                    2023-06-10T16:54:15Z

IRSA 설정

# Create an iamserviceaccount - AWS IAM role bound to a Kubernetes service account
eksctl create iamserviceaccount \
  --name ack-$SERVICE-controller \
  --namespace $ACK_SYSTEM_NAMESPACE \
  --cluster $CLUSTER_NAME \
  --attach-policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`AmazonEC2FullAccess`].Arn' --output text) \
  --override-existing-serviceaccounts --approve

# 확인 >> 웹 관리 콘솔에서 CloudFormation Stack >> IAM Role 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME

# Inspecting the newly created Kubernetes Service Account, we can see the role we want it to assume in our pod.
kubectl get sa -n $ACK_SYSTEM_NAMESPACE
kubectl describe sa ack-$SERVICE-controller -n $ACK_SYSTEM_NAMESPACE

# Restart ACK service controller deployment using the following commands.
kubectl -n $ACK_SYSTEM_NAMESPACE rollout restart deploy ack-$SERVICE-controller-$SERVICE-chart

# IRSA 적용으로 Env, Volume 추가 확인
kubectl describe pod -n $ACK_SYSTEM_NAMESPACE -l k8s-app=$SERVICE-chart
...

VPC, Subnet 생성 및 삭제

# [터미널1] 모니터링
while true; do aws ec2 describe-vpcs --query 'Vpcs[*].{VPCId:VpcId, CidrBlock:CidrBlock}' --output text; echo "-----"; sleep 1; done

# VPC 생성
cat <<EOF > vpc.yaml
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: VPC
metadata:
  name: vpc-tutorial-test
spec:
  cidrBlocks: 
  - 10.0.0.0/16
  enableDNSSupport: true
  enableDNSHostnames: true
EOF
 
kubectl apply -f vpc.yaml
# vpc.ec2.services.k8s.aws/vpc-tutorial-test created

# VPC 생성 확인
kubectl get vpcs
kubectl describe vpcs
aws ec2 describe-vpcs --query 'Vpcs[*].{VPCId:VpcId, CidrBlock:CidrBlock}' --output text


# [터미널1] 모니터링
VPCID=$(kubectl get vpcs vpc-tutorial-test -o jsonpath={.status.vpcID})
while true; do aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" --query 'Subnets[*].{SubnetId:SubnetId, CidrBlock:CidrBlock}' --output text; echo "-----"; sleep 1 ; done

# 서브넷 생성
VPCID=$(kubectl get vpcs vpc-tutorial-test -o jsonpath={.status.vpcID})

cat <<EOF > subnet.yaml
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: Subnet
metadata:
  name: subnet-tutorial-test
spec:
  cidrBlock: 10.0.0.0/20
  vpcID: $VPCID
EOF
kubectl apply -f subnet.yaml

# 서브넷 생성 확인
kubectl get subnets
kubectl describe subnets
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" --query 'Subnets[*].{SubnetId:SubnetId, CidrBlock:CidrBlock}' --output text

# 리소스 삭제
kubectl delete -f subnet.yaml && kubectl delete -f vpc.yaml

VPC Workflow 생성

  • Create a VPC Workflow
  • 다수의, 서로 연계된 리소스를 통한 network topology를 구성하기 위해서, 여러 리소스의 정보가 담긴 YAML 파일 생성
    • VPC, Subnet, SG, RT, EIP, IGW, NATGW, Instance, …
Network Topology
  • VPC Workflow 구성파일 (김)
cat <<EOF > vpc-workflow.yaml
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: VPC
metadata:
  name: tutorial-vpc
spec:
  cidrBlocks: 
  - 10.0.0.0/16
  enableDNSSupport: true
  enableDNSHostnames: true
  tags:
    - key: name
      value: vpc-tutorial
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: InternetGateway
metadata:
  name: tutorial-igw
spec:
  vpcRef:
    from:
      name: tutorial-vpc
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: NATGateway
metadata:
  name: tutorial-natgateway1
spec:
  subnetRef:
    from:
      name: tutorial-public-subnet1
  allocationRef:
    from:
      name: tutorial-eip1
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: ElasticIPAddress
metadata:
  name: tutorial-eip1
spec:
  tags:
    - key: name
      value: eip-tutorial
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: RouteTable
metadata:
  name: tutorial-public-route-table
spec:
  vpcRef:
    from:
      name: tutorial-vpc
  routes:
  - destinationCIDRBlock: 0.0.0.0/0
    gatewayRef:
      from:
        name: tutorial-igw
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: RouteTable
metadata:
  name: tutorial-private-route-table-az1
spec:
  vpcRef:
    from:
      name: tutorial-vpc
  routes:
  - destinationCIDRBlock: 0.0.0.0/0
    natGatewayRef:
      from:
        name: tutorial-natgateway1
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: Subnet
metadata:
  name: tutorial-public-subnet1
spec:
  availabilityZone: ap-northeast-2a
  cidrBlock: 10.0.0.0/20
  mapPublicIPOnLaunch: true
  vpcRef:
    from:
      name: tutorial-vpc
  routeTableRefs:
  - from:
      name: tutorial-public-route-table
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: Subnet
metadata:
  name: tutorial-private-subnet1
spec:
  availabilityZone: ap-northeast-2a
  cidrBlock: 10.0.128.0/20
  vpcRef:
    from:
      name: tutorial-vpc
  routeTableRefs:
  - from:
      name: tutorial-private-route-table-az1
---
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: SecurityGroup
metadata:
  name: tutorial-security-group
spec:
  description: "ack security group"
  name: tutorial-sg
  vpcRef:
     from:
       name: tutorial-vpc
  ingressRules:
    - ipProtocol: tcp
      fromPort: 22
      toPort: 22
      ipRanges:
        - cidrIP: "0.0.0.0/0"
          description: "ingress"
EOF

Provision ACK Resources

  • VPC Workflow 적용
# VPC 환경 생성
kubectl apply -f vpc-workflow.yaml

# [터미널1] NATGW 생성 완료 후 tutorial-private-route-table-az1 라우팅 테이블 ID가 확인되고 그후 tutorial-private-subnet1 서브넷ID가 확인됨 > 5분 정도 시간 소요
watch -d kubectl get routetables,subnet

# VPC 환경 생성 확인
kubectl describe vpcs
kubectl describe internetgateways
kubectl describe routetables
kubectl describe natgateways
kubectl describe elasticipaddresses
kubectl describe securitygroups

# 배포 도중 2개의 서브넷 상태 정보 비교 해보자
kubectl describe subnets
...
# Status:
#   Conditions:
#     Last Transition Time:  2023-06-10T17:27:49Z
#     Message:               Reference resolution failed
#     Reason:                the referenced resource is not synced yet. resource:RouteTable, namespace:default, name:tutorial-public-route-table
#     Status:                Unknown
#     Type:                  ACK.ReferencesResolved
# Events:                    <none>
...
# Status:
#   Ack Resource Metadata:
#     Arn:                       arn:aws:ec2:ap-northeast-2:596152156334:subnet/subnet-0795f261a5d6d82a0
#     Owner Account ID:          596152156334
#     Region:                    ap-northeast-2
#   Available IP Address Count:  4091
#   Conditions:
#     Last Transition Time:           2023-06-10T17:27:49Z
#     Status:                         True
#     Type:                           ACK.ReferencesResolved
#     Last Transition Time:           2023-06-10T17:27:49Z
#     Message:                        Resource synced successfully
#     Reason:                         
#     Status:                         True
#     Type:                           ACK.ResourceSynced
  • 퍼블릭 서브넷에 인스턴스 생성
# public 서브넷 ID 확인
PUBSUB1=$(kubectl get subnets tutorial-public-subnet1 -o jsonpath={.status.subnetID})
echo $PUBSUB1

# 보안그룹 ID 확인
TSG=$(kubectl get securitygroups tutorial-security-group -o jsonpath={.status.id})
echo $TSG

# Amazon Linux 2 최신 AMI ID 확인
AL2AMI=$(aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" --query 'Images[0].ImageId' --output text)
echo $AL2AMI

# 각자 자신의 SSH 키페어 이름 변수 지정
MYKEYPAIR=<각자 자신의 SSH 키페어 이름>
MYKEYPAIR=nasirk17

# 변수 확인 > 특히 서브넷 ID가 확인되었는지 꼭 확인하자!
echo $PUBSUB1 , $TSG , $AL2AMI , $MYKEYPAIR


# [터미널1] 모니터링
while true; do aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table; date ; sleep 1 ; done

# public 서브넷에 인스턴스 생성
cat <<EOF > tutorial-bastion-host.yaml
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: Instance
metadata:
  name: tutorial-bastion-host
spec:
  imageID: $AL2AMI # AL2 AMI ID - ap-northeast-2
  instanceType: t3.medium
  subnetID: $PUBSUB1
  securityGroupIDs:
  - $TSG
  keyName: $MYKEYPAIR
  tags:
    - key: producer
      value: ack
EOF
kubectl apply -f tutorial-bastion-host.yaml

# 인스턴스 생성 확인
kubectl get instance
kubectl describe instance
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
  • public 서브넷에 인스턴스 접속
# ssh -i <자신의 키페어파일> ec2-user@<public 서브넷에 인스턴스 퍼블릭IP>
------
# public 서브넷에 인스턴스 접속 후 외부 인터넷 통신 여부 확인 
ping -c 2 8.8.8.8
exit
------
  • 보안 그룹 정책 수정 : egress 규칙 추가
cat <<EOF > modify-sg.yaml
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: SecurityGroup
metadata:
  name: tutorial-security-group
spec:
  description: "ack security group"
  name: tutorial-sg
  vpcRef:
     from:
       name: tutorial-vpc
  ingressRules:
    - ipProtocol: tcp
      fromPort: 22
      toPort: 22
      ipRanges:
        - cidrIP: "0.0.0.0/0"
          description: "ingress"
  egressRules:
    - ipProtocol: '-1'
      ipRanges:
        - cidrIP: "0.0.0.0/0"
          description: "egress"
EOF
kubectl apply -f modify-sg.yaml

# 변경 확인 >> 보안그룹에 아웃바운드 규칙 확인
kubectl logs -n $ACK_SYSTEM_NAMESPACE -l k8s-app=ec2-chart -f
  • public 서브넷에 인스턴스 접속 후 외부 인터넷 통신 확인
# ssh -i <자신의 키페어파일> ec2-user@<public 서브넷에 인스턴스 퍼블릭IP>
------
# public 서브넷에 인스턴스 접속 후 외부 인터넷 통신 여부 확인 
ping -c 2 8.8.8.8
exit
------
  • 프라이빗 서브넷에 인스턴스 생성
# private 서브넷 ID 확인 >> NATGW 생성 완료 후 RT/SubnetID가 확인되어 다소 시간 필요함
PRISUB1=$(kubectl get subnets tutorial-private-subnet1 -o jsonpath={.status.subnetID})
echo $PRISUB1

# 변수 확인 > 특히 private 서브넷 ID가 확인되었는지 꼭 확인하자!
echo $PRISUB1 , $TSG , $AL2AMI , $MYKEYPAIR

# private 서브넷에 인스턴스 생성
cat <<EOF > tutorial-instance-private.yaml
apiVersion: ec2.services.k8s.aws/v1alpha1
kind: Instance
metadata:
  name: tutorial-instance-private
spec:
  imageID: $AL2AMI # AL2 AMI ID - ap-northeast-2
  instanceType: t3.medium
  subnetID: $PRISUB1
  securityGroupIDs:
  - $TSG
  keyName: $MYKEYPAIR
  tags:
    - key: producer
      value: ack
EOF
kubectl apply -f tutorial-instance-private.yaml

# 인스턴스 생성 확인
kubectl get instance
kubectl describe instance
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
  • public 서브넷에 인스턴스에 SSH 터널링 설정
# ssh -i <자신의 키페어파일> -L <자신의 임의 로컬 포트>:<private 서브넷의 인스턴스의 private ip 주소>:22 ec2-user@<public 서브넷에 인스턴스 퍼블릭IP> -v

ssh -i ~/.ssh/nasirk17.pem -L 9999:10.0.133.61:22 ec2-user@43.202.68.164 -v
---
접속 후 그냥 두기
---
  • 자신의 임의 로컬 포트로 SSH 접속 시, private 서브넷에 인스턴스 접속됨
ssh -i <자신의 키페어파일> -p <자신의 임의 로컬 포트> ec2-user@localhost
ssh -i ~/.ssh/nasirk17.pem -p 9999 ec2-user@localhost
---
# IP 및 네트워크 정보 확인
ip -c addr
sudo ss -tnp
ping -c 2 8.8.8.8
curl ipinfo.io/ip ; echo # 출력되는 공인IP는 무엇인가?
# 43.202.39.219 / NAT Gateway의 공인 IP
exit
---
  • 실습 후 리소스 삭제
kubectl delete -f tutorial-bastion-host.yaml && kubectl delete -f tutorial-instance-private.yaml
kubectl delete -f vpc-workflow.yaml  # vpc 관련 모든 리소스들 삭제에는 다소 시간이 소요됨

  • 실습 결과
  • VPC Workflow로 생성된 리소스들 확인 (k get -f vpc-workflow.yaml)
  • 퍼블릭, 프라이빗 인스턴스 조회
  • ssh 터널링 확인
  • NAT Gateway IP 확인
aws ec2 describe-nat-gateways --query "NatGateways[*].{Name: Tags[?Key=='Name']|[0].Value, PrivateIPAdd: NatGatewayAddresses[*].PrivateIp, PublicIPAdd: NatGatewayAddresses[*].PublicIp}" --output table

# Name까지 가져와서 깔끔하게 가로로된 표로 출력하는 방법 찾아보기
# aws ec2 describe-nat-gateways --query "NatGateways[].NatGatewayAddresses[].{PublicIPAdd:PublicIp,PrivateIPAdd:PrivateIp,Name:Tags[?Key=='Name']|[0].Value}" --output table

2.3. RDS 실습

  • 지원 엔진
    • Aurora(MySQL & PostgreSQL)
    • RDS for PostgreSQL
    • RDS for MySQL
    • RDS for MariaDB
    • RDS for Oracle
    • RDS for SQL Server

ACK RDS Controller 설치 with Helm

# 서비스명 변수 지정 및 helm 차트 다운로드
export SERVICE=rds
export RELEASE_VERSION=$(curl -sL https://api.github.com/repos/aws-controllers-k8s/$SERVICE-controller/releases/latest | grep '"tag_name":' | cut -d'"' -f4 | cut -c 2-)
helm pull oci://public.ecr.aws/aws-controllers-k8s/$SERVICE-chart --version=$RELEASE_VERSION
tar xzvf $SERVICE-chart-$RELEASE_VERSION.tgz

# helm chart 확인
tree ~/$SERVICE-chart

# ACK EC2-Controller 설치
export ACK_SYSTEM_NAMESPACE=ack-system
export AWS_REGION=ap-northeast-2
helm install -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller --set aws.region="$AWS_REGION" ~/$SERVICE-chart

# 설치 확인
helm list --namespace $ACK_SYSTEM_NAMESPACE
kubectl -n $ACK_SYSTEM_NAMESPACE get pods -l "app.kubernetes.io/instance=ack-$SERVICE-controller"
kubectl get crd | grep $SERVICE
# dbclusterparametergroups.rds.services.k8s.aws   2023-06-10T18:32:24Z
# dbclusters.rds.services.k8s.aws                 2023-06-10T18:32:24Z
# dbinstances.rds.services.k8s.aws                2023-06-10T18:32:24Z
# dbparametergroups.rds.services.k8s.aws          2023-06-10T18:32:24Z
# dbproxies.rds.services.k8s.aws                  2023-06-10T18:32:24Z
# dbsubnetgroups.rds.services.k8s.aws             2023-06-10T18:32:24Z
# globalclusters.rds.services.k8s.aws             2023-06-10T18:32:24Z

IRSA 설정

# Create an iamserviceaccount - AWS IAM role bound to a Kubernetes service account
eksctl create iamserviceaccount \
  --name ack-$SERVICE-controller \
  --namespace $ACK_SYSTEM_NAMESPACE \
  --cluster $CLUSTER_NAME \
  --attach-policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`AmazonRDSFullAccess`].Arn' --output text) \
  --override-existing-serviceaccounts --approve

# 확인 >> 웹 관리 콘솔에서 CloudFormation Stack >> IAM Role 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME

# Inspecting the newly created Kubernetes Service Account, we can see the role we want it to assume in our pod.
kubectl get sa -n $ACK_SYSTEM_NAMESPACE
kubectl describe sa ack-$SERVICE-controller -n $ACK_SYSTEM_NAMESPACE

# Restart ACK service controller deployment using the following commands.
kubectl -n $ACK_SYSTEM_NAMESPACE rollout restart deploy ack-$SERVICE-controller-$SERVICE-chart

# IRSA 적용으로 Env, Volume 추가 확인
kubectl describe pod -n $ACK_SYSTEM_NAMESPACE -l k8s-app=$SERVICE-chart
...

AWS RDS for MariaDB 생성


# DB 암호를 위한 secret 생성
# RDS_INSTANCE_NAME="<your instance name>"
# RDS_INSTANCE_PASSWORD="<your instance password>"
RDS_INSTANCE_NAME=myrds
RDS_INSTANCE_PASSWORD=qwe12345
kubectl create secret generic "${RDS_INSTANCE_NAME}-password" --from-literal=password="${RDS_INSTANCE_PASSWORD}"

# 확인
kubectl get secret $RDS_INSTANCE_NAME-password

# [터미널1] 모니터링
RDS_INSTANCE_NAME=myrds
watch -d "kubectl describe dbinstance "${RDS_INSTANCE_NAME}" | grep 'Db Instance Status'"

# RDS 배포 생성 : 15분 이내 시간 소요 >> 보안그룹, 서브넷 등 필요한 옵션들은 추가해서 설정해보자!
cat <<EOF > rds-mariadb.yaml
apiVersion: rds.services.k8s.aws/v1alpha1
kind: DBInstance
metadata:
  name: "${RDS_INSTANCE_NAME}"
spec:
  allocatedStorage: 20
  dbInstanceClass: db.t4g.micro
  dbInstanceIdentifier: "${RDS_INSTANCE_NAME}"
  engine: mariadb
  engineVersion: "10.6"
  masterUsername: "admin"
  masterUserPassword:
    namespace: default
    name: "${RDS_INSTANCE_NAME}-password"
    key: password
EOF
kubectl apply -f rds-mariadb.yaml

# 생성 확인
kubectl get dbinstances  ${RDS_INSTANCE_NAME}
kubectl describe dbinstance "${RDS_INSTANCE_NAME}"
aws rds describe-db-instances --db-instance-identifier $RDS_INSTANCE_NAME | jq

kubectl describe dbinstance "${RDS_INSTANCE_NAME}" | grep 'Db Instance Status'
  Db Instance Status:         creating

kubectl describe dbinstance "${RDS_INSTANCE_NAME}" | grep 'Db Instance Status'
  Db Instance Status:         backing-up

kubectl describe dbinstance "${RDS_INSTANCE_NAME}" | grep 'Db Instance Status'
  Db Instance Status:         available

# 생성 완료 대기 : for 지정 상태가 완료되면 정상 종료됨
kubectl wait dbinstances ${RDS_INSTANCE_NAME} --for=condition=ACK.ResourceSynced --timeout=15m
dbinstance.rds.services.k8s.aws/myrds condition met

MariaDB 접속

  • fieldexport 리소스 생성
  • fieldexport:
    • ACK 상의 리소스와, k8s 리소스 간의 연동을 위한 리소스
    • ACK 리소스의 spec/status field를 k8s configmap/secret으로 export
      > pod,deploy 등 다른 리소스에서 k8s cm/secret을 가져와 연동 가능
RDS_INSTANCE_CONN_CM="${RDS_INSTANCE_NAME}-conn-cm"

cat <<EOF > rds-field-exports.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: ${RDS_INSTANCE_CONN_CM}
data: {}
---
apiVersion: services.k8s.aws/v1alpha1
kind: FieldExport
metadata:
  name: ${RDS_INSTANCE_NAME}-host
spec:
  to:
    name: ${RDS_INSTANCE_CONN_CM}
    kind: configmap
  from:
    path: ".status.endpoint.address"
    resource:
      group: rds.services.k8s.aws
      kind: DBInstance
      name: ${RDS_INSTANCE_NAME}
---
apiVersion: services.k8s.aws/v1alpha1
kind: FieldExport
metadata:
  name: ${RDS_INSTANCE_NAME}-port
spec:
  to:
    name: ${RDS_INSTANCE_CONN_CM}
    kind: configmap
  from:
    path: ".status.endpoint.port"
    resource:
      group: rds.services.k8s.aws
      kind: DBInstance
      name: ${RDS_INSTANCE_NAME}
---
apiVersion: services.k8s.aws/v1alpha1
kind: FieldExport
metadata:
  name: ${RDS_INSTANCE_NAME}-user
spec:
  to:
    name: ${RDS_INSTANCE_CONN_CM}
    kind: configmap
  from:
    path: ".spec.masterUsername"
    resource:
      group: rds.services.k8s.aws
      kind: DBInstance
      name: ${RDS_INSTANCE_NAME}
EOF

kubectl apply -f rds-field-exports.yaml
# 상태 정보 확인 : address 와 port 정보 
kubectl get dbinstances myrds -o jsonpath={.status.endpoint} | jq
# {
#   "address": "myrds.cynfq6t5vatm.ap-northeast-2.rds.amazonaws.com",
#   "hostedZoneID": "ZLA2NUCOLGUUR",
#   "port": 3306
# }

# 상태 정보 확인 : masterUsername 확인
kubectl get dbinstances myrds -o jsonpath={.spec.masterUsername} ; echo

# 컨피그맵 확인
kubectl get cm myrds-conn-cm -o yaml | kubectl neat | yh
# apiVersion: v1
# data: 
#   default.myrds-host: myrds.cynfq6t5vatm.ap-northeast-2.rds.amazonaws.com
#   default.myrds-port: "3306"
#   default.myrds-user: admin
# kind: ConfigMap
# metadata: 
#   name: myrds-conn-cm
#   namespace: default

# fieldexport 정보 확인
kubectl get crd | grep fieldexport
kubectl get fieldexport
kubectl get fieldexport myrds-host -o yaml | k neat | yh
  • RDS 사용 파드 생성
APP_NAMESPACE=default
cat <<EOF > rds-pods.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app
  namespace: ${APP_NAMESPACE}
spec:
  containers:
   - image: busybox
     name: myapp
     command:
        - sleep
        - "3600"
     imagePullPolicy: IfNotPresent
     env:
      - name: DBHOST
        valueFrom:
         configMapKeyRef:
          name: ${RDS_INSTANCE_CONN_CM}
          key: "${APP_NAMESPACE}.${RDS_INSTANCE_NAME}-host"
      - name: DBPORT
        valueFrom:
         configMapKeyRef:
          name: ${RDS_INSTANCE_CONN_CM}
          key: "${APP_NAMESPACE}.${RDS_INSTANCE_NAME}-port"
      - name: DBUSER
        valueFrom:
         configMapKeyRef:
          name: ${RDS_INSTANCE_CONN_CM}
          key: "${APP_NAMESPACE}.${RDS_INSTANCE_NAME}-user"
      - name: DBPASSWORD
        valueFrom:
          secretKeyRef:
           name: "${RDS_INSTANCE_NAME}-password"
           key: password
EOF
kubectl apply -f rds-pods.yaml

# 생성 확인
kubectl get pod app

# 파드의 환경 변수 확인
kubectl exec -it app -- env | grep DB
# DBUSER=admin
# DBPASSWORD=qwe12345
# DBHOST=myrds.cynfq6t5vatm.ap-northeast-2.rds.amazonaws.com
# DBPORT=3306
  • RDS db 식별자 변경 확인
# [터미널]
watch -d "kubectl get dbinstance; echo; kubectl get cm myrds-conn-cm -o yaml | kubectl neat"

# 아래 처럼 RDS 에서 직접 변경 할 경우 rds controller 를 별도 추적을 하지 않아서, k8s 상태와 aws 상태 정보가 깨져버럼
# DB 식별자 변경 : studyend 
aws rds modify-db-instance --db-instance-identifier $RDS_INSTANCE_NAME --new-db-instance-identifier studyend --apply-immediately

# DB 식별자를 업데이트 >> 어떤 현상이 발생하는가?
# 변경된 DB 식별자에 해당하는 DB클러스터가 없기 때문에, 새로운 RDS 클러스터 생성
kubectl patch dbinstance myrds --type=merge -p '{"spec":{"dbInstanceIdentifier":"studyend"}}'

# 확인
kubectl get dbinstance myrds
kubectl describe dbinstance myrds
  • 최종 변경 정보 반영 확인
# 상태 정보 확인 : address 변경 확인!
kubectl get dbinstances myrds -o jsonpath={.status.endpoint} | jq
# {
#   "address": "studyend.cynfq6t5vatm.ap-northeast-2.rds.amazonaws.com",
#   "hostedZoneID": "ZLA2NUCOLGUUR",
#   "port": 3306
# }

# 파드의 환경 변수 확인 >> 파드의 경우 환경 변수 env로 정보를 주입했기 때문에 변경된 정보를 확인 할 수 없다
kubectl exec -it app -- env | grep DB
# DBUSER=admin
# DBPASSWORD=qwe12345
# DBHOST=myrds.cynfq6t5vatm.ap-northeast-2.rds.amazonaws.com
# DBPORT=3306

# 파드 삭제 후 재생성 후 확인
kubectl delete pod app && kubectl apply -f rds-pods.yaml

# 파드의 환경 변수 확인 >> 변경 정보 확인!
# 즉 deployments, daemonsets, statefulsets 의 경우 rollout 으로 env 변경 적용을 할 수 는 있겠다!
kubectl exec -it app -- env | grep DB
# DBPORT=3306
# DBUSER=admin
# DBPASSWORD=qwe12345
# DBHOST=studyend.cynfq6t5vatm.ap-northeast-2.rds.amazonaws.com

  • 실습 결과
  • DB 인스턴스 식별자 변경 > 기존 RDS외에, 변경된 식별자에 해당하는 RDS 생성

  • pod 재생성을 통한 환경 변수 변경 확인


3. Flux

Flux는 Kubernetes 클러스터를 구성 소스(예: Git repo 및 OCI 아티팩트)와 동기화 상태로 유지하고 배포할 새 코드가 있을 때 구성 업데이트를 자동화하는 도구이다.

Flux 버전 2(“v2”)는 처음부터 Kubernetes의 API 확장 시스템을 사용하도록 구축되었으며, Prometheus 및 기타 Kubernetes 에코시스템의 핵심 구성 요소와 통합되도록 만들어졌다. 버전 2에서 Flux는 멀티테넌시와 임의의 수의 Git 리포지토리 동기화 지원 등 오랫동안 요청되어 온 여러 가지 기능을 지원한다.

Flux v2는 컴포저블 API와 Kubernetes 위에 지속적 배포를 구축하기 위한 전문 도구의 집합인 GitOps 툴킷으로 구성됩니다.

Flux는 다양한 조직과 클라우드 제공업체에서 프로덕션에 사용하는 CNCF(Cloud Native Computing Foundation) 프로젝트입니다.

Flux is a tool for keeping Kubernetes clusters in sync with sources of configuration (like Git repositories and OCI artifacts), and automating updates to configuration when there is new code to deploy.

Flux version 2 (“v2”) is built from the ground up to use Kubernetes’ API extension system, and to integrate with Prometheus and other core components of the Kubernetes ecosystem. In version 2, Flux supports multi-tenancy and support for syncing an arbitrary number of Git repositories, among other long-requested features.

Flux v2 is constructed with the GitOps Toolkit, a set of composable APIs and specialized tools for building Continuous Delivery on top of Kubernetes.

Flux is a Cloud Native Computing Foundation (CNCF) project, used in production by various organisations and cloud providers.

Flux version2, Github

Flux CLI 설치 및 Bootstrap

# Flux CLI 설치
curl -s https://fluxcd.io/install.sh | sudo bash
. <(flux completion bash)

# 버전 확인
flux --version
# flux version 2.0.0-rc.5

# 자신의 Github 토큰과 유저이름 변수 지정
# export GITHUB_TOKEN=<your-token>
# export GITHUB_USER=<your-username>
export GITHUB_TOKEN=ghp_###
export GITHUB_USER=nasir17git

# Bootstrap
## Creates a git repository fleet-infra on your GitHub account.
## Adds Flux component manifests to the repository.
## Deploys Flux Components to your Kubernetes Cluster.
## Configures Flux components to track the path /clusters/my-cluster/ in the repository.
flux bootstrap github \
  --owner=$GITHUB_USER \
  --repository=fleet-infra \
  --branch=main \
  --path=./clusters/my-cluster \
  --personal

# 설치 확인
kubectl get pods -n flux-system
kubectl get-all -n flux-system
kubectl get crd | grep fluxc
kubectl get gitrepository -n flux-system
# NAME          URL                                           AGE   READY   STATUS
# flux-system   ssh://git@github.com/nasir17git/fleet-infra   40s   True    stored artifact for revision 'main@sha1:db1ed2a08fb3f67da2fc4c7258982'

gitops 도구 설치

# gitops 도구 설치
curl --silent --location "https://github.com/weaveworks/weave-gitops/releases/download/v0.24.0/gitops-$(uname)-$(uname -m).tar.gz" | tar xz -C /tmp
sudo mv /tmp/gitops /usr/local/bin
gitops version

# flux 대시보드 설치
PASSWORD="password"
gitops create dashboard ww-gitops --password=$PASSWORD

# 확인
flux -n flux-system get helmrelease
kubectl -n flux-system get pod,svc
  • Ingress 설정
CERT_ARN=`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text`
echo $CERT_ARN

# Ingress 설정
cat <<EOT > gitops-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: gitops-ingress
  annotations:
    alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
    alb.ingress.kubernetes.io/group.name: study
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
    alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/ssl-redirect: "443"
    alb.ingress.kubernetes.io/success-codes: 200-399
    alb.ingress.kubernetes.io/target-type: ip
spec:
  ingressClassName: alb
  rules:
  - host: gitops.$MyDomain
    http:
      paths:
      - backend:
          service:
            name: ww-gitops-weave-gitops
            port:
              number: 9001
        path: /
        pathType: Prefix
EOT
kubectl apply -f gitops-ingress.yaml -n flux-system

# 배포 확인
kubectl get ingress -n flux-system

# GitOps 접속 정보 확인 >> 웹 접속 후 정보 확인
echo -e "GitOps Web https://gitops.$MyDomain"

hello world (kubstomize)

  • 악분님 Github
  • github에 있는 nginx manifest를 쿠버네티스에 배포합니다. 배포할 때 kusotmize를 사용
# 소스 생성 : 유형 - git, helm, oci, bucket
# flux create source {소스 유형}
# 악분(최성욱)님이 준비한 repo로 git 소스 생성
GITURL="https://github.com/sungwook-practice/fluxcd-test.git"
flux create source git nginx-example1 --url=$GITURL --branch=main --interval=30s

# 소스 확인
flux get sources git
kubectl -n flux-system get gitrepositories
  • flux 애플리케이션 생성 : 유형(kustomization) , 깃 소스 경로( —path ./nginx) → gitops 웹 대시보드에서 확인
# [터미널] 모니터링
watch -d kubectl get pod,svc nginx-example1

# flux 애플리케이션 생성 : nginx-example1
flux create kustomization nginx-example1 --target-namespace=default --interval=1m --source=nginx-example1 --path="./nginx" --health-check-timeout=2m

# 확인
kubectl get pod,svc nginx-example1
kubectl get kustomizations -n flux-system
flux get kustomizations
  • 애플리케이션 삭제
# [터미널] 모니터링
watch -d kubectl get pod,svc nginx-example1

# flux 애플리케이션 삭제 >> 파드와 서비스는? flux 애플리케이션 생성 시 --prune 옵션 false(default 값)
# 파드와 서비스는 남아있음
flux delete kustomization nginx-example1
flux get kustomizations
kubectl get pod,svc nginx-example1

# flux 애플리케이션 다시 생성 :  --prune 옵션 true
flux create kustomization nginx-example1 \
  --target-namespace=default \
  --prune=true \
  --interval=1m \
  --source=nginx-example1 \
  --path="./nginx" \
  --health-check-timeout=2m

# 확인
flux get kustomizations
kubectl get pod,svc nginx-example1

# flux 애플리케이션 삭제 >> 파드와 서비스는? 
flux delete kustomization nginx-example1
flux get kustomizations
kubectl get pod,svc nginx-example1

# flux 소스 삭제
flux delete source git nginx-example1

# 소스 확인
flux get sources git
kubectl -n flux-system get gitrepositories

공식 Docs 샘플 실습

  • Clone the git repository
# Clone the git repository : 자신의 Github 의 Username, Token 입력
git clone https://github.com/$GITHUB_USER/fleet-infra
# Username for 'https://github.com': <자신의 Github 의 Username>
# Password for 'https://nasir17git@github.com': <자신의 Github의 Token>

# 폴더 이동
cd fleet-infra
tree
# .
# └── clusters
#     └── my-cluster
#         └── flux-system
#             ├── gotk-components.yaml
#             ├── gotk-sync.yaml
#             └── kustomization.yaml
# 
# 3 directories, 3 files
# GitRepository yaml 파일 생성
flux create source git podinfo \
  --url=https://github.com/stefanprodan/podinfo \
  --branch=master \
  --interval=30s \
  --export > ./clusters/my-cluster/podinfo-source.yaml

# GitRepository yaml 파일 확인
cat ./clusters/my-cluster/podinfo-source.yaml | yh

# Commit and push the podinfo-source.yaml file to the fleet-infra repository >> Github 확인
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git add -A && git commit -m "Add podinfo GitRepository"
git push
# Username for 'https://github.com': <자신의 Github 의 Username>
# Password for 'https://nasir17git@github.com': <자신의 Github의 Token>

# 소스 확인
flux get sources git
kubectl -n flux-system get gitrepositories
# [터미널]
watch -d kubectl get pod,svc

# Use the flux create command to create a Kustomization that applies the podinfo deployment.
flux create kustomization podinfo \
  --target-namespace=default \
  --source=podinfo \
  --path="./kustomize" \
  --prune=true \
  --interval=5m \
  --export > ./clusters/my-cluster/podinfo-kustomization.yaml

# 파일 확인
cat ./clusters/my-cluster/podinfo-kustomization.yaml | yh

# ---
# apiVersion: kustomize.toolkit.fluxcd.io/v1
# kind: Kustomization
# metadata: 
#   name: podinfo
#   namespace: flux-system
# spec: 
#   interval: 5m0s
#   path: ./kustomize
#   prune: true
#   sourceRef: 
#     kind: GitRepository
#     name: podinfo
#   targetNamespace: default

# Commit and push the Kustomization manifest to the repository:
git add -A && git commit -m "Add podinfo Kustomization"
git push

# 확인
kubectl get pod,svc
kubectl get kustomizations -n flux-system
flux get kustomizations
tree
# .
# └── clusters
#     └── my-cluster
#         ├── flux-system
#         │   ├── gotk-components.yaml
#         │   ├── gotk-sync.yaml
#         │   └── kustomization.yaml
#         ├── podinfo-kustomization.yaml
#         └── podinfo-source.yaml
# 
# 3 directories, 5 files
  • Watch Flux sync the application
# [터미널]
watch -d kubectl get pod,svc

# 파드 갯수 변경 시도 >> 어떻게 되는가?
# 잠시 변경되어도, flux 설정값인 2로 복귀
kubectl scale deployment podinfo --replicas 1
...
kubectl scale deployment podinfo --replicas 3
...
  • 삭제
#
flux delete kustomization podinfo
flux delete source git podinfo

#
flux uninstall --namespace=flux-system

  • 실습 결과

  • gitops 대시보드 구성 및 배포 후, Kind/Source/Path 확인


  • k8s 리소스 <> flux 어플리케이션 동기화 테스트
동기화 이전(prune=false) > flux 상에서 지워도, k8s 리소스는 남아있음
동기화 이후 (prune=true) > flux 상에서 지우면, 연동된 k8s 리소스 삭제
  • podinfo scale 테스트
    • replica 변경시, 잠시 파드의 변경이 발생하나 바로 설정된 replica로 롤백됨.

3. Outro

또 이렇게 7주간의 AEWS 스터디가 마무리되었다. 시작시 갖고있었던 목표인 ‘EKS Workshop을 단순히 복붙으로 끝내는게 아니라, 연관 자료들 찾아보면서, 하나씩 곱씹어보면서 소화해보기’를 나름 성공적으로 마친것 같아서 뿌듯하다.

  • 생각하는 엔지니어가 되도록 노력하기

매주 스터디 자료를 작성하면서 힘썼던 부분 중 하나는 ‘왜 이 주제가 하나의 챕터가 되었고, 이것을 배우는게 무슨 의미가 있는가’를 나름의 방식으로 정리하는 것이였다.

지인 중 한 명이 최근 블로그 스터디글을 보고 ‘기술 블로그인데 왜 이렇게 배경 지식에 힘을 많이 쏟느냐, 좀 더 어떻게 구현했는지 그 과정과, 기술에 딥 다이브하는게 좋지 않겠느냐’라는 피드백을 주었었다. 생각하던 방향이랑 달라서 조금 머쓱해지긴 했지만, 그래도 내 방식도 틀리지는 않았다고 생각한다.

특정 기술/도구/방법론을 ‘어떻게’ 구현했느냐 보다는 ‘무엇을’, ‘왜’ 구현했는지에 대한 답을 먼저 찾고 싶었다. 사실 이부분은 요즘 하는 다른 고민인 ‘잘한다는 것은 무엇일까, 잘한다는 것을 어떻게 정의할 수 있으며, 어떻게 그 기준에 맞게 성장할 수 있을까’와 이어지는 부분이지만… 이번주차처럼 ACK와 Flux라는 도구가 주어졌을때 이것을 빠르게 클러스터에 설치하고 많은 어플리케이션과 연동하는 사람이 잘하는 사람일까? 그렇지 않다고 생각한다. 적용하는 것은 공식문서/블로그/chatgpt를 참조하고 디버깅하다보면 마무리할 수 있다. 잘하는 사람은 주어진 도구가 적절한지, 왜 적용해야하는지 판단할 수 있는 사람이라고 생각한다. 그래서 중고등학교 교과서부터가 (물론 그때는 또 안읽고 문제부터 풀었지만) 시작시 무엇을 배울거고, 왜 배우는지에 대해 많은 시간/분량을 할애한다고 생각하고…

  • 주어진 인풋을 얼마나 소화하는지는 그사람의 역량이지만, 인풋이 없으면 아웃풋도 없다.

스터디 중반즈음부터 진짜 다들 너무 잘하셔서 살짝 의지가 꺾였었다. 매주 스터디를 리딩해주시는 가시다님은 말할것도 없고, 다른 발표해주시는 분들의 공유해주는 이야기를 들으면서 ‘나도 저렇게까지 될 수 있을까’하고 조금 먹먹해졌었다.

바로 직전에 PKOS 스터디를 하고 온 만큼 EKS는 금방 씹어먹을 줄 알았는데, AWS랑 연동되는 부분은 그 자체로도 하나의 벽이였다.(특히 IRSA) 가시다님의 설명과 발표자분들의 사례공유가 10의 input이라면, 나는 얼만큼의 output을 내고 있는가, 얼마나 내지식으로 소화하고 흡수하고 있지와 부족한 소화력의 원인인 기본기를(네트워크/리눅스/AWS등) 좀더 다듬고 와야하나라는 고민을 했었다.

하지만 지금은 손에서 흘러나간 것들을 보기보다는 손에 남아있는 것들을 봐야하지 않겠냐고 생각한다. 쥐려고 노력하지 않았으면 아무것도 잡을 수 없었을테니 말이다.

7주동안 매주마다 스터디를 리딩해주셔서 감사합니다! 고생많으셨습니다. 다음 스터디때에도 이것저것 쥐어보려 다시 또 신청하겠습니다!

Leave a Reply