AEWS Study #7 – EKS Automation

이번 챕터는 EKS 스터디의 마지막 챕터인 EKS Automation이다. 7주라는 짧은 시간 동안 여러 내용들을 학습해왔는데 이번 주는 배웠던 내용들을 토대로 자동화룰 구성하고 GitOps 환경을 구성하는 내용으로 진행하게 된다.

0. 환경 구성

이번 환경 구성은 지난 번과 마찬가지로 특별하게 변경하는 내용은 없고 yaml 파일로 기본 환경 배포 및 External DNS 설치와 prometheus-community, Metrics-server 설치를 진행한다.

1. AWS Controller for Kubernetes (ACK)

AWS Controller for Kubernetes (ACK)는 AWS 리소스와 k8s을 통합하여 Cluster 내에서 AWS 서비스를 관리하고 프로비저닝하는 기능을 제공하는 AWS에서 개발한 오픈소스 프로젝트이다.
ACK는 AWS 리소스를 k8s의 CRD(Custom Resource Definition)으로 표현하고, AWS 리소스와 k8s 리소스 간의 상태를 동기화하는 방식으로 작동한다. 이를 통해 개발자와 운영자는 k8s API을 통해 AWS 서비스 관리가 가능하다.
ACK는 AWS의 다양한 서비스(S3, RDS, DynamoDB, ECR, EKS, SNS, SQS 등)을 지원한다.

https://aws-controllers-k8s.github.io/community/docs/community/how-it-works/

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

https://aws-controllers-k8s.github.io/community/docs/user-docs/authorization/

ACK를 이용해서 AWS의 각종 서비스들을 배포해보는 실습을 진행한다.
ACK을 이용한 AWS 서비스 배포 과정은 서비스들 전체적으로 비슷하게 이루어진다.
ACK Controller 설치 w/helm (각 서비스에 맞게) -> IRSA 권한 설정 -> AWS 서비스 배포

1-1. ACK for S3

우선 S3을 먼저 배포하고 테스트할 예정이다.
ACK S3 Controller 설치를 진행한다. helm 차트를 다운로드 받고 해당 helm chart을 사용해서 ACK S3 Controller을 설치하고 확인한다. 설치에 사용되는 변수는 SERVICE, ACK_SYSTEM_NAMESPACE, AWS_REGION이 사용된다. EC2나 RDS 등 다른 서비스를 배포할 때도 ACK Controller을 설치해야 하는데 이때 다른 변수는 동일하게 두고 SERVICE만 바꿔서 설치해서 구분지어주면 좋다.

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

# helm 차트 다운로드
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
NAME             	NAMESPACE 	REVISION	UPDATED                                	STATUS  	CHART         	APP VERSION
ack-s3-controller	ack-system	1       	2023-06-04 21:12:06.857876402 +0900 KST	deployed	s3-chart-1.0.4	1.0.4

kubectl -n ack-system get pods
NAME                                          READY   STATUS    RESTARTS   AGE
ack-s3-controller-s3-chart-7c55c6657d-mbrq5   1/1     Running   0          10s

kubectl get crd | grep $SERVICE
buckets.s3.services.k8s.aws                  2023-06-04T12:12:04Z

kubectl get all -n ack-system
NAME                                              READY   STATUS    RESTARTS   AGE
pod/ack-s3-controller-s3-chart-7c55c6657d-mbrq5   1/1     Running   0          30s
NAME                                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ack-s3-controller-s3-chart   1/1     1            1           30s
NAME                                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/ack-s3-controller-s3-chart-7c55c6657d   1         1         1       30s

kubectl get-all -n ack-system
kubectl describe sa -n ack-system ack-s3-controller
Name:                ack-s3-controller
Namespace:           ack-system
Labels:              app.kubernetes.io/instance=ack-s3-controller
                     app.kubernetes.io/managed-by=Helm
....

AWS 서비스 배포 및 관리 권한을 얻기 위해 IRSA 설정을 진행한다. 실습에서는 AmazonS3FullAccess 권한을 사용해서 진행하도록 한다.

# 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
NAMESPACE	NAME				ROLE ARN
ack-system	ack-s3-controller		arn:aws:iam::MyAccount:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-XFQ0LIYFYJ3Z

# 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
NAME                SECRETS   AGE
ack-s3-controller   0         5m16s

kubectl describe sa ack-$SERVICE-controller -n ack-system
Name:                ack-s3-controller
Namespace:           ack-system
Labels:              app.kubernetes.io/instance=ack-s3-controller
                     app.kubernetes.io/managed-by=eksctl
...

# 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
Name:             ack-s3-controller-s3-chart-559866764-42xt6
Namespace:        ack-system
Priority:         0
Service Account:  ack-s3-controller
Node:             ip-192-168-1-10.ap-northeast-2.compute.internal/192.168.1.10
...

ACK S3 Controller와 IRSA가 준비됐다면 S3을 배포하고 수정 및 삭제하는 테스트를 진행해본다.
Bucket Name은 중복을 피하기 위해 Account ID을 참조해서 만들고 생성 확인 후 Tag 값을 추가한 업데이트와 삭제를 진행해봤다.
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
apiVersion: s3.services.k8s.aws/v1alpha1
kind: Bucket
metadata:
  name: my-ack-s3-bucket-MyAccount
spec:
  name: my-ack-s3-bucket-MyAccount

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

# S3 버킷 확인
aws s3 ls
2023-06-04 21:21:43 my-ack-s3-bucket-MyAccount

kubectl get buckets
NAME                            AGE
my-ack-s3-bucket-MyAccount   17s

kubectl describe bucket/$BUCKET_NAME | head -6
Name:         my-ack-s3-bucket-MyAccount
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  s3.services.k8s.aws/v1alpha1
Kind:         Bucket

# 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

# 변경 내용 사전 확인
cat bucket.yaml | yh
apiVersion: s3.services.k8s.aws/v1alpha1
kind: Bucket
metadata:
  name: my-ack-s3-bucket-587122150371
spec:
  name: my-ack-s3-bucket-587122150371
  tagging:
    tagSet:
    - key: myTagKey
      value: myTagValue

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

# S3 버킷 업데이트 확인 
kubectl describe bucket/$BUCKET_NAME | grep Spec: -A5
Spec:
  Name:  my-ack-s3-bucket-MyAccount
  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

S3의 생성, 업데이트와 삭제을 확인 완료했다면 ACK S3 Controller와 IRSA을 삭제하도록 한다. 물론, 다른 서비스의 Controller, IRSA와 겹치지 않게 배포할 수 있기 때문에 꼭 삭제를 사전에 진행할 필요는 없다.

# 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

##### namespace 삭제 >> ACK 모든 실습 후 삭제  #####
kubectl delete namespace $ACK_K8S_NAMESPACE

1-2. ACK for EC2&VPC

S3는 간단하게 Bucket만 배포하고 업데이트, 삭제하는 부분이라 어렵지 않았는데 이번엔 EC2를 배포하려고 한다. EC2의 경우 배포하려면 VPC부터 시작해서 여러 서비스들과 결합이 필요하기 때문에 꽤 상세한 설정이 필요하다.
순서는 ACK EC2 Controller 설치-> EC2 IRSA 설정(AmazonEC2FullAccess) -> VPC, Subnet 생성 -> Public/Private Subnet EC2 배포 -> 테스트 순으로 진행한다.

우선 ACK EC2 Controller 설치를 진행한다. 특이한 내용은 없고 SERVICE 변수를 위에서 실습한 s3에서 ec2로 변경하고 진행하면 된다.

# 서비스명 변수 지정 및 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
NAME: ack-ec2-controller
LAST DEPLOYED: Sun Jun  4 21:46:39 2023
NAMESPACE: ack-system
STATUS: deployed

# 설치 확인
helm list --namespace $ACK_SYSTEM_NAMESPACE
NAME              	NAMESPACE 	REVISION	UPDATED                               	STATUS  	CHART          	APP VERSION
ack-ec2-controller	ack-system	1       	2023-06-04 21:46:39.80730411 +0900 KST	deployed	ec2-chart-1.0.3	1.0.3

kubectl -n $ACK_SYSTEM_NAMESPACE get pods -l "app.kubernetes.io/instance=ack-$SERVICE-controller"
NAME                                            READY   STATUS    RESTARTS   AGE
ack-ec2-controller-ec2-chart-777567ff4c-45s2d   1/1     Running   0          21s

kubectl get crd | grep $SERVICE
dhcpoptions.ec2.services.k8s.aws             2023-06-04T12:46:38Z
elasticipaddresses.ec2.services.k8s.aws      2023-06-04T12:46:38Z
instances.ec2.services.k8s.aws               2023-06-04T12:46:38Z
internetgateways.ec2.services.k8s.aws        2023-06-04T12:46:38Z
natgateways.ec2.services.k8s.aws             2023-06-04T12:46:39Z
routetables.ec2.services.k8s.aws             2023-06-04T12:46:39Z
securitygroups.ec2.services.k8s.aws          2023-06-04T12:46:39Z
subnets.ec2.services.k8s.aws                 2023-06-04T12:46:39Z
transitgateways.ec2.services.k8s.aws         2023-06-04T12:46:39Z
vpcendpoints.ec2.services.k8s.aws            2023-06-04T12:46:39Z
vpcs.ec2.services.k8s.aws                    2023-06-04T12:46:39Z

IRSA 설정 또한 s3 때와 크게 다르지 않다. 권한만 AmazonS3FullAccess에서 AmazonEC2FullAccess로 변경하여 진행한다. IRSA 설정 후 AWS Console에서 IAM Role에서 생성 된 Role을 확인하면 AmazonEC2FullAccess가 잘 들어간 것을 확인할 수 있다.

# 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
NAMESPACE	NAME				ROLE ARN
ack-system	ack-ec2-controller		arn:aws:iam::MyAccount:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-VK23PW7Y288N

# 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
NAME                 SECRETS   AGE
ack-ec2-controller   0         3m45s

kubectl describe sa ack-$SERVICE-controller -n $ACK_SYSTEM_NAMESPACE
Name:                ack-ec2-controller
Namespace:           ack-system
Labels:              app.kubernetes.io/instance=ack-ec2-controller
                     app.kubernetes.io/managed-by=eksctl
                     app.kubernetes.io/name=ec2-chart
...

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

# IRSA 적용으로 Env, Volume 추가 확인
kubectl describe pod -n $ACK_SYSTEM_NAMESPACE -l k8s-app=$SERVICE-chart
Name:             ack-ec2-controller-ec2-chart-76dd69c88-62kfs
Namespace:        ack-system
Priority:         0
Service Account:  ack-ec2-controller
Node:             ip-192-168-1-10.ap-northeast-2.compute.internal/192.168.1.10
...

EC2 배포에 사용할 VPC, Subnet 그리고 IGW와 SG등을 생성하기 전에 테스트로 VPC, Subnet을 배포하는 실습을 진행해보았다.

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

# 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

-----
192.168.0.0/16	vpc-0ab487a4560d0e009
172.31.0.0/16	vpc-04663e26208f8fb7d
10.0.0.0/16	vpc-0cc88d9792531d28b
-----

# VPC 생성 확인
kubectl get vpcs
NAME                ID                      STATE
vpc-tutorial-test   vpc-0cc88d9792531d28b   available

kubectl describe vpcs
Name:         vpc-tutorial-test
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  ec2.services.k8s.aws/v1alpha1
...

aws ec2 describe-vpcs --query 'Vpcs[*].{VPCId:VpcId, CidrBlock:CidrBlock}' --output text
192.168.0.0/16	vpc-0ab487a4560d0e009
172.31.0.0/16	vpc-04663e26208f8fb7d
10.0.0.0/16	vpc-0cc88d9792531d28b

# [터미널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
-----
10.0.0.0/20	subnet-03d5af788ba04433d
-----

# 서브넷 생성 확인
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, Subnet을 생성해봤으니 이제 EC2 배포에 필요할 리소스들을 배포해보도록 한다. AWS Network Resource를 배포할 때는 workflow을 작성하는 게 좋다. VPC CIDR, Subnet CIDR(w/AZ)와 Public/Private Subnet 구분 그리고 IGW와 SG을 생성하고 Subnet에서의 정상적인 트래픽 흐름을 위해 RT 까지 같이 설정해준다. 이번 실습에서는 샘플로 제공되는 Workflow을 사용한다.

https://aws-controllers-k8s.github.io/community/docs/tutorials/ec2-example/#create-a-vpc-workflow

위 그림과 같은 흐름으로 Network Workflow을 배포할 예정이다. 내용을 보면 tutorial-vpc(10.0.0.0/16)을 생성하고 그 안에 Public(10.0.0.0/20)/Private(10.0.128.0/20)을 하나씩 생성한다.
SG는 any IP에 대해 22 port 접근을 허용하고 Internet Gateway, NAT Gateway(w/EIP)을 각각 생성한다. 그리고 RouteTable은 Public Subnet용 RT는 0.0.0.0/0에 대해 IGW로 연결하고 Private Subnet용 RT는 0.0.0.0/0에 대해 NAT Gateway로 연결해준다.
샘플로 제공 된 yaml을 사용하여 배포하도록 한다.

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

yaml을 생성했으면 해당 yaml을 갖고 VPC 환경을 배포하도록 한다. Private Subnet의 경우 AWS Console에서 배포할 때도 그렇지만 NAT GW의 배포가 완료되기 전까지 정보 확인이 완료되지 않는다. AWS Console에서 확인하는 VPC 구성도에서도 subnet이 하나만 표현 된다. 이후 시간이 지나고 NAT GW가 배포되고 Private Subnet의 정보가 반영되면 AWS Console에서도 정상적으로 화면이 나타난다.

# 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
Name:         tutorial-vpc
Namespace:    default
...
kubectl describe internetgateways
Name:         tutorial-igw
Namespace:    default
...
kubectl describe routetables
Name:         tutorial-private-route-table-az1
Namespace:    default
...
kubectl describe natgateways
Name:         tutorial-natgateway1
Namespace:    default
...
kubectl describe elasticipaddresses
Name:         tutorial-eip1
Namespace:    default
...
kubectl describe securitygroups
Name:         tutorial-security-group
Namespace:    default
...

# 배포 도중 2개의 서브넷 상태 정보 비교 해보자
kubectl describe subnets
...
Status:
  Conditions:
    Last Transition Time:  2023-06-04T13:03:04Z
    Message:               Reference resolution failed
    Reason:                the referenced resource is not synced yet. resource:RouteTable, namespace:default, name:tutorial-private-route-table-az1
    Status:                Unknown
    Type:                  ACK.ReferencesResolved
...
Status:
  Ack Resource Metadata:
    Arn:                       arn:aws:ec2:ap-northeast-2:MyAccount:subnet/subnet-03cd0923f8663f1ee
    Owner Account ID:          MyAccount
    Region:                    ap-northeast-2
  Available IP Address Count:  4091
  Conditions:
    Last Transition Time:           2023-06-04T13:01:45Z
    Status:                         True
    Type:                           ACK.ReferencesResolved
    Last Transition Time:           2023-06-04T13:01:45Z
    Message:                        Resource synced successfully
    Reason:
    Status:                         True
    Type:                           ACK.ResourceSynced
...

VPC workflow 배포가 완료되면 Public Subnet에 EC2를 배포하고 테스트해보도록 한다.
배포에는 SubnetID, SGID, AMI ID 그리고 접속에 필요한 Keypair을 사용하여 배포를 진행한다. 배포 이전에는 bastion ec2와 Node Group에 속한 EC2들만 확인이 되고 배포를 진행하면 추가로 테스트 EC2가 보이는 것을 확인할 수 있다.

# public 서브넷 ID 확인
PUBSUB1=$(kubectl get subnets tutorial-public-subnet1 -o jsonpath={.status.subnetID})
echo $PUBSUB1
subnet-03cd0923f8663f1ee

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

# 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
ami-0a0453c1a1758acf1

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

# 변수 확인 > 특히 서브넷 ID가 확인되었는지 꼭 확인하자!
echo $PUBSUB1 , $TSG , $AL2AMI , $MYKEYPAIR
subnet-03cd0923f8663f1ee , sg-0992f933b69408947 , ami-0a0453c1a1758acf1 , aewspair

# [터미널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
+----------------+-----------------+------------------+----------+
|  InstanceName  |  PrivateIPAdd   |   PublicIPAdd    | Status   |
+----------------+-----------------+------------------+----------+
|  myeks-ng1-Node|  192.168.3.165  |  54.180.xxx.xxx  |  running |
|  myeks-ng1-Node|  192.168.2.5    |  3.35.xxx.xxx    |  running |
|  myeks-ng1-Node|  192.168.1.10   |  43.201.xxx.xxx  |  running |
|  myeks-bastion |  192.168.1.100  |  54.180.xxx.xxx  |  running |
+----------------+-----------------+------------------+----------+
Sun Jun  4 22:09:42 KST 2023

# 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
instance.ec2.services.k8s.aws/tutorial-bastion-host created


# 인스턴스 생성 확인
kubectl get instance
NAME                    ID
tutorial-bastion-host   i-070849e21be1aa100

kubectl describe instance
Name:         tutorial-bastion-host
Namespace:    default

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
+----------------+-----------------+------------------+----------+
|  InstanceName  |  PrivateIPAdd   |   PublicIPAdd    | Status   |
+----------------+-----------------+------------------+----------+
|  myeks-ng1-Node|  192.168.3.165  |  54.180.xxx.xxx  |  running |
|  myeks-ng1-Node|  192.168.2.5    |  3.35.xxx.xxx    |  running |
|  myeks-ng1-Node|  192.168.1.10   |  43.201.xxx.xxx  |  running |
|  myeks-bastion |  192.168.1.100  |  54.180.xxx.xxx  |  running |
|  None          |  10.0.5.137     |  13.209.xxx.xxx  |  running |
+----------------+-----------------+------------------+----------+
Sun Jun  4 22:10:42 KST 2023

Public Subnet에 EC2를 배포했으니 Client에서 해당 EC2에 접속하고 egress(ping 8.8.8.8)을 테스트해보았다.
현재 SG에는 egress 허용이 되어있지 않아 8.8.8.8로 Ping이 나가지 않는 것을 확인할 수 있다.

ssh -i <자신의 키페어파일> ec2-user@<public 서브넷에 인스턴스 퍼블릭IP>
------
# public 서브넷에 인스턴스 접속 후 외부 인터넷 통신 여부 확인 
ping -c 2 8.8.8.8
exit
------

SG에 대한 egress 설정도 할 겸 ACK Controller을 활용해서 생성만 진행했으니 업데이트를 진행해보도록 한다. 설정은 기존 SG에 0.0.0.0/0에 대해 Egress 허용 정책을 추가하였다.

# SG 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
securitygroup.ec2.services.k8s.aws/tutorial-security-group configured

# 변경 확인 >> 보안그룹에 아웃바운드 규칙 확인
kubectl logs -n $ACK_SYSTEM_NAMESPACE -l k8s-app=ec2-chart -f
2023-06-04T13:16:36.970Z	INFO	ackrt	desired resource state has changed	{"account": "MyAccount", "role": "", "region": "ap-northeast-2", "kind": "SecurityGroup", "namespace": "default", "name": "tutorial-security-group", "is_adopted": false, "generation": 2, "diff": [{"Path":{"Parts":["Spec","EgressRules"]},"A":[{"ipProtocol":"-1","ipRanges":[{"cidrIP":"0.0.0.0/0","description":"egress"}]}],"B":null}]}
2023-06-04T13:16:37.217Z	INFO	ackrt	updated resource	{"account": "MyAccount", "role": "", "region": "ap-northeast-2", "kind": "SecurityGroup", "namespace": "default", "name": "tutorial-security-group", "is_adopted": false, "generation": 2}

SG Egress 설정 업데이트를 했으니 다시 8.8.8.8에 대한 ping 확인도 진행해보았다. 정상적으로 ping이 나가는 것을 보아 Egress 정책이 잘 적용되었음을 확인할 수 있었다. 그리고 EC2의 IP 정보를 확인하였고 이때 Public IP가 정상적으로 호출되는 것을 확인할 수 있었다.

ssh -i <자신의 키페어파일> ec2-user@<public 서브넷에 인스턴스 퍼블릭IP>
------
# public 서브넷에 인스턴스 접속 후 외부 인터넷 통신 여부 확인 
ping -c 10 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=46 time=17.0 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=46 time=17.1 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=46 time=17.1 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=46 time=17.1 ms
...
--- 8.8.8.8 ping statistics ---
10 packets transmitted, 10 received, 0% packet loss, time 9011ms
rtt min/avg/max/mdev = 17.089/17.135/17.164/0.156 ms

curl ipinfo.io/ip ; echo
13.209.xxx.xxx
exit
------

Public Subnet에 EC2을 배포하고 테스트해봤으니 이번에는 Private Subnet에 인스턴스를 배포하고 테스트해보도록 한다.
Private Subnet에 배포하는 것도 Public Subnet에 배포하는 것과 동일하게 SubnetID, SGID, AMIID, Keypir가 필요하다.
배포하고 내용을 조회해보면 Public IP 없이 Private IP만 갖은 채로 생성된 것을 확인할 수 있다.

# private 서브넷 ID 확인
PRISUB1=$(kubectl get subnets tutorial-private-subnet1 -o jsonpath={.status.subnetID})
echo $PRISUB1
subnet-0997be6d046a7f8c7

# 변수 확인 > 특히 private 서브넷 ID가 확인되었는지 꼭 확인하자!
echo $PRISUB1 , $TSG , $AL2AMI , $MYKEYPAIR
subnet-0997be6d046a7f8c7 , sg-0992f933b69408947 , ami-0a0453c1a1758acf1 , aewspair

# 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
instance.ec2.services.k8s.aws/tutorial-instance-private created

# 인스턴스 생성 확인 (위에서 만든 Public Subnet EC2는 bastion, 방금 만든 EC2는 instance-private)
kubectl get instance
NAME                        ID
tutorial-bastion-host       i-070849e21be1aa100
tutorial-instance-private   i-088273052920e222b

kubectl describe instance
Name:         tutorial-instance-private
Namespace:    default

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
+----------------+-----------------+------------------+----------+
|  InstanceName  |  PrivateIPAdd   |   PublicIPAdd    | Status   |
+----------------+-----------------+------------------+----------+
|  myeks-ng1-Node|  192.168.3.165  |  54.180.xxx.xxx  |  running |
|  myeks-ng1-Node|  192.168.2.5    |  3.35.xxx.xxx    |  running |
|  myeks-ng1-Node|  192.168.1.10   |  43.201.xxx.xxx  |  running |
|  myeks-bastion |  192.168.1.100  |  54.180.xxx.xxx  |  running |
|  None          |  10.0.5.137     |  13.209.xxx.xxx  |  running |
|  None          |  10.0.128.130   |  None            |  running |
+----------------+-----------------+------------------+----------+

Private Subnet에 배포 된 EC2에는 현재 환경에선 곧바로 접속할 수 있는 방법이 없다. VPN 등을 연결하면 가능하나 현재 VPC Workflow에는 그런 구성이 되어있지 않으니 기존에 배포한 Public Subnet의 EC2에 SSH 터널링을 설정해서 해당 EC2을 거쳐 Private Subnet EC2에 접속하도록 한다.
이때 2개의 터미널 창을 사용할 예정인데 EKS Bastion에 접속한 터미널1, 모니터용 터미널2을 제외한 SSH 터널링용 터미널3와 Private Subnet EC2에 SSH 터널링을 타고 접속할 터미널4을 사용한다.

SSH 터널링을 통해 Private EC2에 접속을 하게 되면 8.8.8.8에 대한 Egress도 제대로 작동하고 ss -tnp을 통해 연결 된 포트를 확인해보면 Public EC2와 22번 Port을 통해 연결 된 것을 확인할 수 있다.
그리고 IP을 확인하면 Public IP을 확인할 수 있는데 해당 IP는 Public EC2의 IP가 아닌 Private EC2가 외부로 나갈 때 사용하는 NAT GW의 Public IP이니 참고하자.

# [터미널3] SSH 터널링 설정
ssh -i <자신의 키페어파일> -L <자신의 임의 로컬 포트>:<private 서브넷의 인스턴스의 private ip 주소>:22 ec2-user@<public 서브넷에 인스턴스 퍼블릭IP> -v
ssh -i aewspair.pem -L 9999:10.0.128.130:22 ec2-user@13.209.xxx.xxx -v
---

# [터미널4] SSH 터널링 통해 Private Subnet EC2 접속
ssh -i <자신의 키페어파일> -p <자신의 임의 로컬 포트> ec2-user@localhost
ssh -i aewspair.pem -p 9999 ec2-user@localhost
---
# IP 및 네트워크 정보 확인
ip -c addr
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
    link/ether 02:93:25:a2:64:64 brd ff:ff:ff:ff:ff:ff
    inet 10.0.128.130/20 brd 10.0.143.255 scope global dynamic eth0

sudo ss -tnp
State       Recv-Q       Send-Q              Local Address:Port                Peer Address:Port        Process
ESTAB       0            0                    10.0.128.130:22                    10.0.5.137:38416        users:(("sshd",pid=2516,fd=3),("sshd",pid=2499,fd=3))

ping -c 2 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=103 time=28.7 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=103 time=27.9 ms
--- 8.8.8.8 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 27.994/28.396/28.799/0.436 ms

curl ipinfo.io/ip ; echo 
43.201.xxx.xxx
exit
---

실습이 완료됐으니 리소스 삭제를 진행한다.

# 리소스 삭제
kubectl delete -f tutorial-bastion-host.yaml && kubectl delete -f tutorial-instance-private.yaml
kubectl delete -f vpc-workflow.yaml  # vpc 관련 모든 리소스들 삭제에는 다소 시간이 소요됨

# 리소스 삭제 확인
kubectl get instance
No resources found in default namespace.

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
+----------------+-----------------+------------------+----------+
|  InstanceName  |  PrivateIPAdd   |   PublicIPAdd    | Status   |
+----------------+-----------------+------------------+----------+
|  myeks-ng1-Node|  192.168.3.165  |  54.180.xxx.xxx  |  running |
|  myeks-ng1-Node|  192.168.2.5    |  3.35.xxx.xxx    |  running |
|  myeks-ng1-Node|  192.168.1.10   |  43.201.xxx.xxx  |  running |
|  myeks-bastion |  192.168.1.100  |  54.180.xxx.xxx  |  running |
+----------------+-----------------+------------------+----------+

1-3. ACK for RDS

S3와 EC2과 같이 AWS에서 많이 쓰이는 서비스인 RDS에 대해서도 실습을 진행해보려고 한다. RDS의 엔진 대부분을 ACK에서 지원하는데 이번 실습에서는 RDS for MariaDB으로 실습을 진행한다.
다른 서비스들과 마찬가지로 ACK Controller 설치 및 IRSA 구성과 서비스 배포, 업데이트 그리고 삭제까지 진행할 예정이다.

# 서비스명 변수 지정 및 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 RDS-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
NAME              	NAMESPACE 	REVISION	UPDATED                                	STATUS  	CHART          	APP VERSION
ack-ec2-controller	ack-system	1       	2023-06-04 21:46:39.80730411 +0900 KST 	deployed	ec2-chart-1.0.3	1.0.3
ack-rds-controller	ack-system	1       	2023-06-05 08:24:20.821985798 +0900 KST	deployed	rds-chart-1.1.4	1.1.4

kubectl -n $ACK_SYSTEM_NAMESPACE get pods -l "app.kubernetes.io/instance=ack-$SERVICE-controller"
NAME                                            READY   STATUS              RESTARTS   AGE
ack-rds-controller-rds-chart-6d59dfdfd7-k2mnl   0/1     ContainerCreating   0          2s

kubectl get crd | grep $SERVICE
dbclusterparametergroups.rds.services.k8s.aws   2023-06-04T23:24:19Z
dbclusters.rds.services.k8s.aws                 2023-06-04T23:24:19Z
dbinstances.rds.services.k8s.aws                2023-06-04T23:24:20Z
dbparametergroups.rds.services.k8s.aws          2023-06-04T23:24:20Z
dbproxies.rds.services.k8s.aws                  2023-06-04T23:24:20Z
dbsubnetgroups.rds.services.k8s.aws             2023-06-04T23:24:20Z
globalclusters.rds.services.k8s.aws             2023-06-04T23:24:20Z

ACK Controller 설치를 완료했다면 IRSA 설정을 진행한다. 권한은 AmazonRDSFullAccess을 부여했다.

# 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
NAMESPACE	NAME				ROLE ARN
ack-system	ack-ec2-controller		arn:aws:iam::MyAccount:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-VK23PW7Y288N
ack-system	ack-rds-controller		arn:aws:iam::MyAccount:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-AUTL0REK98XM
ack-system	ack-s3-controller		arn:aws:iam::MyAccount:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-XFQ0LIYFYJ3Z

# 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
NAME                 SECRETS   AGE
ack-ec2-controller   0         10h
ack-rds-controller   0         5m2s

kubectl describe sa ack-$SERVICE-controller -n $ACK_SYSTEM_NAMESPACE
Name:                ack-rds-controller
Namespace:           ack-system
Labels:              app.kubernetes.io/instance=ack-rds-controller
                     app.kubernetes.io/managed-by=eksctl
                     app.kubernetes.io/name=rds-chart

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

# IRSA 적용으로 Env, Volume 추가 확인
kubectl describe pod -n $ACK_SYSTEM_NAMESPACE -l k8s-app=$SERVICE-chart
Name:             ack-rds-controller-rds-chart-5dfbf7dccb-dlwdv
Namespace:        ack-system
Priority:         0
Service Account:  ack-rds-controller
Node:             ip-192-168-2-5.ap-northeast-2.compute.internal/192.168.2.5
...

RDS for MariaDB 생성테스트를 진행해본다. DB 생성에 필요한 secret 생성을 먼저 생성하고 RDS을 배포했다.

# 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}"
secret/myrds-password created

# 확인
kubectl get secret $RDS_INSTANCE_NAME-password
NAME             TYPE     DATA   AGE
myrds-password   Opaque   1      10s

# [터미널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}
NAME    STATUS
myrds   creating

kubectl describe dbinstance "${RDS_INSTANCE_NAME}"
Name:         myrds
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  rds.services.k8s.aws/v1alpha1
...

aws rds describe-db-instances --db-instance-identifier $RDS_INSTANCE_NAME | jq
  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 배포가 완료됐다면 RDS 연결하는 Pod를 배포하고 해당 Pod에서 RDS의 정보를 제대로 받아오는지를 테스트할 예정이다.
이때 FieldExport는 ACK의 컨트롤러 구성 파일인 awscdk.FieldExport을 사용할 예정이다. FieldExport는 CDK(Cloud Development Kit)를 사용하여 AWS 리소스를 프로비저닝하고 관리할 때 사용한다.
FieldExport를 사용하면 AWS 리소스의 특정 속성을 다른 스택이나 리소스에서 참조할 수 있고 일반적으로 리소스의 출력값을 다른 리소스의 입력값으로 전달하거나 스택 간에 데이터를 공유하는 데 사용한다. 이 특성을 활용하여 k8s Pod에서 좀 전에 배포한 RDS의 변수를 받아올 수 있도록 할 예정이다.
아래 내용이 fieldexport을 사용해서 위에서 배포한 RDS에 대한 configmap을 설정하는 내용이다.

# Configmap 구성 전 Configmap 확인
kubectl get configmaps
NAME               DATA   AGE
kube-root-ca.crt   1      12h

# Configmap 구성
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

# Configmap 구성 확인
NAME               DATA   AGE
kube-root-ca.crt   1      12h
myrds-conn-cm      3      2m35s

fieldexport로 Configmap 구성이 끝났다면 해당 구성 내용을 확인해본다. 위에서 설정한대로 잘 구성된 것을 확인할 수 있다.

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

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

# 컨피그맵 확인
kubectl get cm myrds-conn-cm -o yaml | kubectl neat | yh
apiVersion: v1
data:
  default.myrds-host: myrds.cnlc7....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
fieldexports.services.k8s.aws                   2023-06-04T12:46:39Z

kubectl get fieldexport
NAME         AGE
myrds-host   3m8s
myrds-port   3m8s
myrds-user   3m8s

kubectl get fieldexport myrds-host -o yaml | k neat | yh

RDS의 정보를 읽어올 Pod을 생성한다. Pod의 Image는 가볍게 실행할 busybox을 서낵하고 환경변수에 위에 Configmap을 구성한 내용들(HOST, PORT, USER, PASSWORD)을 참조해서 배포한다.

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
NAME   READY   STATUS    RESTARTS   AGE
app    1/1     Running   0          5s

# 파드의 환경 변수 확인
kubectl exec -it app -- env | grep DB
DBUSER=admin
DBPASSWORD=qwe12345
DBHOST=myrds.cnlc7l....ap-northeast-2.rds.amazonaws.com
DBPORT=3306

앞서 배포한 RDS for MariaDB의 DB 식별자를 업데이트한다. 해당 업데이트 내용을 위에서 배포한 Pod에서는 제대로 인식하는지 확인해도록 한다.
DB식별자 업데이트 명령을 ACK을 통해 진행했고 AWS Console과 터미널 화면에서 모두 변경되는 내용을 확인할 수 있었다.

# [터미널]
watch -d "kubectl get dbinstance; echo; kubectl get cm myrds-conn-cm -o yaml | kubectl neat"

apiVersion: v1
data:
  default.myrds-host: myrds.cnlc7....ap-northeast-2.rds.amazonaws.com
  default.myrds-port: "3306"
  default.myrds-user: admin

# DB 식별자를 업데이트 >> 어떤 현상이 발생하는가?
kubectl patch dbinstance myrds --type=merge -p '{"spec":{"dbInstanceIdentifier":"studyend"}}'
# 상태가 creating -> backing-up -> available로 변경되면서 DB식별자도 myrds에서 studyend로 업데이트 됨.
NAME    STATUS
myrds   creating
NAME    STATUS
myrds   backing-up
NAME    STATUS
myrds   available
apiVersion: v1
data:
  default.myrds-host: studyend.cnlc7...ap-northeast-2.rds.amazonaws.com

# 확인
kubectl get dbinstance myrds
NAME    STATUS
myrds   available

kubectl describe dbinstance myrds
Name:         myrds
Namespace:    default
...
  Endpoint:
    Address:                            studyend.cnlc7lcs9gjt.ap-northeast-2.rds.amazonaws.com
...

식별자 업데이트를 완료했으니 해당 내용을 Pod에서도 인식하는지 확인해보았다.
Pod에는 환경 변수로 해당 정보를 주입했기 때문에 내용이 반영되지 않았음을 확인할 수 있다.
이를 반영시키기 위해선 rollout으로 env 변경을 적용시키거나 삭제 후 재생성하는 방법을 사용해야 함을 알 수 있다.

# 상태 정보 확인 : address 변경 확인!
kubectl get dbinstances myrds -o jsonpath={.status.endpoint} | jq
{
  "address": "studyend.cnlc7lc....ap-northeast-2.rds.amazonaws.com",
  "hostedZoneID": "ZLA2NU...",
  "port": 3306
}

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

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

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

Pod 재배포로 해당 내용 제대로 받아오는 것을 확인했으니 실습 리소스를 삭제하도록 한다.
처음 만들어졌던 DB식별자 myrds는 AWS Console에서 직접 삭제하거나 AWS CLI로 삭제하도록 한다.

# 파드 삭제
kubectl delete pod app

# RDS 삭제 
kubectl delete -f rds-mariadb.yaml

# db식별자 myrds 삭제는 AWS CLI나 AWS Console에서 진행
aws rds delete-db-instance --db-instance-identifier myrds --skip-final-snapshot

1-4. ACK for DynamoDB

기본 실습으로 제공 된 S3, EC2, RDS 제외한 리소스를 배포해보는 것을 도전해보려고 한다.
간단하게 배포 및 테스트할 수 있는 DynamoDB를 테스트해볼 예정이다.
테스트 순서는 다른 리소스들과 동일하다.

SERVICE 변수는 dynamodb로 변경한 후 helm chart 다운로드 및 ACK Controller 설치를 진행한다.

# 서비스명 변수 지정 및 helm 차트 다운로드
export SERVICE=dynamodb
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 dynamodb-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
NAME                 	NAMESPACE 	REVISION	UPDATED                                	STATUS  	CHART             	APP VERSION
ack-ec2-controller   	ack-system	1       	2023-06-04 21:46:39.80730411 +0900 KST 	deployed	ec2-chart-1.0.3   	1.0.3
ack-dynamodb-controller	ack-system	1       	2023-06-05 11:22:44.98087282 +0900 KST 	deployed	dynamodb-chart-1.1.1	1.1.1
ack-rds-controller   	ack-system	1       	2023-06-05 08:24:20.821985798 +0900 KST	deployed	rds-chart-1.1.4   	1.1.4

kubectl -n $ACK_SYSTEM_NAMESPACE get pods -l "app.kubernetes.io/instance=ack-$SERVICE-controller"
NAME                                                      READY   STATUS    RESTARTS   AGE
ack-dynamodb-controller-dynamodb-chart-779c6458d8-wq7tc   1/1     Running   0          6m31s

kubectl get crd | grep $SERVICE
backups.dynamodb.services.k8s.aws               2023-06-05T02:22:44Z
globaltables.dynamodb.services.k8s.aws          2023-06-05T02:22:44Z
tables.dynamodb.services.k8s.aws                2023-06-05T02:22:44Z

IRSA도 기존 다른 서비스들과 동일하게 진행하면서 권한은 AmazonDynamoDBFullAccess로 정의한다.

# 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==`AmazonDynamoDBFullAccess`].Arn' --output text) \
  --override-existing-serviceaccounts --approve

# 확인 >> 웹 관리 콘솔에서 CloudFormation Stack >> IAM Role 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
NAMESPACE	NAME				ROLE ARN
ack-system	ack-dynamodb-controller		arn:aws:iam::MyAccount:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-199XY74UI3PSY
ack-system	ack-ec2-controller		arn:aws:iam::MyAccount:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-VK23PW7Y288N
ack-system	ack-rds-controller		arn:aws:iam::MyAccount:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-AUTL0REK98XM
ack-system	ack-s3-controller		arn:aws:iam::MyAccount:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-XFQ0LIYFYJ3Z

# 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
NAME                    SECRETS   AGE
ack-dynamodb-controller   0         116s
ack-ec2-controller      0         12h
ack-rds-controller      0         124m

kubectl describe sa ack-$SERVICE-controller -n $ACK_SYSTEM_NAMESPACE
Name:                ack-dynamodb-controller
Namespace:           ack-system
Labels:              app.kubernetes.io/managed-by=eksctl
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::587122150371:role/eksctl-myeks-addon-iamserviceaccount-ack-sys-Role1-199XY74UI3PSY

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

# IRSA 적용으로 Env, Volume 추가 확인
kubectl describe pod -n $ACK_SYSTEM_NAMESPACE -l k8s-app=$SERVICE-chart
Name:                      ack-dynamodb-controller-dynamodb-chart-779c6458d8-wq7tc
Namespace:                 ack-system
Priority:                  0
Service Account:           ack-dynamodb-controller
Node:                      ip-192-168-1-10.ap-northeast-2.compute.internal/192.168.1.10
...

ACK Controller 설치 및 IRSA 설정이 완료됐다면 DynamoDB을 배포하도록 한다.
DynamoDB을 배포하고 제대로 배포가 됐는지 테스트해봤다.

# DynamoDB 배포
cat <<EOF > dynamodb.yaml
apiVersion: dynamodb.services.k8s.aws/v1alpha1
kind: Table
metadata:
  name: my-dynamodb-table
  namespace: ack-system
spec:
  tableName: my-dynamodb-table
  billingMode: PAY_PER_REQUEST
  attributeDefinitions:
    - attributeName: id
      attributeType: S
  keySchema:
    - attributeName: id
      keyType: HASH
EOF
kubectl apply -f dynamodb.yaml
table.dynamodb.services.k8s.aws/my-dynamodb-table created

# AWS CLI 통해 배포 확인
aws dynamodb list-tables
{
    "TableNames": [
        "my-dynamodb-table"
    ]
}

# kubectl ACK 통해 확인
kubectl get table -n ack-system
NAME                CLASS   STATUS   SYNCED   AGE
my-dynamodb-table           ACTIVE   True     2m36s

배포와 확인까지 ACK을 통해 잘 진행했다면 이제 dynamodb 테이블에 업데이트도 진행해본다. 간단하게 S3 때와 마찬가지로 Tag 생성을 해보았다.
배포할 때 사용한 내용과 유사하게 만들고 태그값만 추가해줬다. 문제없이 Tag가 추가된 것을 확인할 수 있었다.

# Tag을 입력하기 위한 Yaml 작성 및 배포
cat <<EOF > dynamodb-update.yaml
apiVersion: dynamodb.services.k8s.aws/v1alpha1
kind: Table
metadata:
  name: my-dynamodb-table
  namespace: ack-system
spec:
  tableName: my-dynamodb-table
  billingMode: PAY_PER_REQUEST
  attributeDefinitions:
    - attributeName: id
      attributeType: S
  keySchema:
    - attributeName: id
      keyType: HASH
  tags:
    - key: myTagKey
      value: myNewTagValue
EOF
kubectl apply -f dynamodb-update.yaml

# dynamodb Tag 확인
ddbARN=$(aws dynamodb describe-table --table-name my-dynamodb-table --query 'Table.TableArn' --output text)
echo $ddbARN

aws dynamodb list-tags-of-resource --resource-arn $ddbARN
{
    "Tags": [
        {
            "Key": "myTagKey",
            "Value": "myNewTagValue"
        },
        {
            "Key": "services.k8s.aws/controller-version",
            "Value": "dynamodb-v1.1.1"
        },
        {
            "Key": "services.k8s.aws/namespace",
            "Value": "ack-system"
        }
    ]
}

 

간단하게 dynamodb에 대해 테스트를 진행해보았다.
다른 서비스들과 같이 dynamodb을 삭제하는 것까지해서 테스트를 마무리하려 한다.

# dynamodb table 삭제
kubectl delete table my-dynamodb-table -n ack-system
table.dynamodb.services.k8s.aws "my-dynamodb-table" deleted

kubectl get table -n ack-system
No resources found in ack-system namespace.

aws dynamodb list-tables
{
    "TableNames": []
}

# ACK ddb controller 및 IRSA 삭제
helm uninstall -n $ACK_SYSTEM_NAMESPACE ack-$SERVICE-controller
kubectl delete -f ~/$SERVICE-chart/crds
eksctl delete iamserviceaccount --cluster myeks --name ack-$SERVICE-controller --namespace ack-system

dynamodb 까지 포함해서 ACK을 통해 AWS 리소스를 배포하고 업데이트/삭제까지 진행을 해보았다. 아직은 조금 부족한 Controller라고 느껴진 게 동기화도 조금 느렸고 ACK로 배포하고 나서 AWS Console에서 수정하면 해당 내용은 반영되지 않는 등 아직은 정합성이 조금 아쉬운 느낌이다.

2. Flux

Flux CLI을 설치해서 GitOps 관리를 해보려고 한다.
Flux는 k8s Cluster에서 GitOps 방식으로 애플리케이션 배포 및 관리를 자동화하기 위한 도구이다. GitOps는 애플리케이션의 상태 및 구성을 Git 저장소에 기록하고, 이를 통해 모든 변경 사항을 추적하고 배포하는 DevOps 방법론인데 Flux는 이러한 GitOps 워크플로우를 간편하게 구현하도록 지원한다.
Flux는 Kubernetes 클러스터에서 실행되며, Helm과 같은 패키지 관리자와 함께 사용할 수 있다. Flux는 Kubernetes의 Custom Resource Definition을 사용하여 Flux의 구성 및 동작을 설명하는 YAML 파일을 정의한다.
GitOps를 통해 애플리케이션 배포와 관리를 자동화하고, 구성 관리를 통해 신뢰성과 일관성을 확보할 수 있다. Flux는 이러한 GitOps 워크플로우를 구현하는 데 도움을 주는 강력한 도구 중 하나이다
Flux의 특징은 다음과 같다.

  1. GitOps Workflow: Flux는 애플리케이션의 배포 및 구성 정보를 Git 저장소에 저장하고 이를 통해 변경 이력을 관리하고, 모든 변경은 Git 저장소를 통해 추적한다.
  2. 자동 배포 및 롤백: Flux는 Git 저장소의 변경 사항을 감지하고, 변경된 내용을 기반으로 자동으로 애플리케이션을 배포한다. 롤백도 Git 저장소의 이전 상태로 간단히 수행할 수 있다.
  3. Declarative Configuration: Flux는 Kubernetes의 Custom Resource Definition(CRD)을 사용하여 애플리케이션 배포에 대한 선언적인 구성을 제공한다. 이를 통해 애플리케이션 및 인프라스트럭처의 상태를 코드로 관리할 수 있다.
  4. 다중 환경 및 브랜치 관리: Flux는 여러 개발 환경(예: 개발, 스테이징, 프로덕션) 및 Git 브랜치에 대한 배포를 지원한다. 이를 통해 개발자는 각 환경 및 브랜치에 맞는 애플리케이션 구성을 유지할 수 있다.
  5. Synchronization: Flux는 Kubernetes 클러스터와 Git 저장소 간에 지속적인 동기화를 유지한다. 즉, Git 저장소의 변경 사항을 즉시 반영하고, 클러스터와 저장소 간의 일관성을 유지한다.
  6. Hooks 및 Automation: Flux는 이벤트 트리거(Hooks)를 통해 배포 이벤트를 자동화할 수 있다. 예를 들어, 애플리케이션 배포 후에 특정 작업을 수행하거나, 외부 도구와의 통합을 위한 작업을 자동으로 실행할 수 있다.

Flux을 통해 GitOps Workflow을 하기 위해 Flux CLI설치를 먼저 진행한다.
설치 시에 github Token 정보를 등록하는데 나는 모든 권한을 부여한 Token을 생성했다.

# Flux CLI 설치
curl -s https://fluxcd.io/install.sh | sudo bash
[INFO]  Downloading metadata https://api.github.com/repos/fluxcd/flux2/releases/latest
[INFO]  Using 2.0.0-rc.5 as release
[INFO]  Downloading hash https://github.com/fluxcd/flux2/releases/download/v2.0.0-rc.5/flux_2.0.0-rc.5_checksums.txt
[INFO]  Downloading binary https://github.com/fluxcd/flux2/releases/download/v2.0.0-rc.5/flux_2.0.0-rc.5_linux_amd64.tar.gz
[INFO]  Verifying binary download
which: no shasum in (/sbin:/bin:/usr/sbin:/usr/bin)
[INFO]  Installing flux to /usr/local/bin/flux
. <(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=myname

# 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
✔ Kustomization reconciled successfully
► confirming components are healthy
✔ helm-controller: deployment ready
✔ kustomize-controller: deployment ready
✔ notification-controller: deployment ready
✔ source-controller: deployment ready
✔ all components are healthy

# 설치 확인
kubectl get pods -n flux-system
NAME                                       READY   STATUS    RESTARTS   AGE
helm-controller-fbdd59577-chxns            1/1     Running   0          48s
kustomize-controller-6b67b54cf8-mbb8z      1/1     Running   0          48s
notification-controller-78f4869c94-tftpm   1/1     Running   0          48s
source-controller-75db64d9f7-rdjw8         1/1     Running   0          48s

kubectl get-all -n flux-system
kubectl get crd | grep fluxc
alerts.notification.toolkit.fluxcd.io           2023-06-05T10:19:45Z
buckets.source.toolkit.fluxcd.io                2023-06-05T10:19:45Z
gitrepositories.source.toolkit.fluxcd.io        2023-06-05T10:19:45Z
helmcharts.source.toolkit.fluxcd.io             2023-06-05T10:19:45Z
helmreleases.helm.toolkit.fluxcd.io             2023-06-05T10:19:46Z
helmrepositories.source.toolkit.fluxcd.io       2023-06-05T10:19:46Z
kustomizations.kustomize.toolkit.fluxcd.io      2023-06-05T10:19:46Z
ocirepositories.source.toolkit.fluxcd.io        2023-06-05T10:19:46Z
providers.notification.toolkit.fluxcd.io        2023-06-05T10:19:46Z
receivers.notification.toolkit.fluxcd.io        2023-06-05T10:19:46Z

kubectl get gitrepository -n flux-system
NAME          URL                                       AGE   READY   STATUS
flux-system   ssh://git@github.com/myname/fleet-infra   74s   True    stored artifact for revision 'main@sha1:ede8721252d83c8f4....'

FluxCLI 설치가 완료됐으니 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
Current Version: 0.24.0
GitCommit: cc1d0e680c55e0aaf5bfa0592a0a454fb2064bc1
BuildTime: 2023-05-24T16:29:14Z
Branch: releases/v0.24.0

# flux 대시보드 설치
PASSWORD="password"
gitops create dashboard ww-gitops --password=$PASSWORD
✔ Flux &{v2.0.0-rc.5  flux-system} is already installed
► Applying GitOps Dashboard manifests
► Installing the GitOps Dashboard ...
✔ GitOps Dashboard has been installed
► Request reconciliation of dashboard (timeout 3m0s) ...
◎ Waiting for GitOps Dashboard reconciliation
✔ GitOps Dashboard ww-gitops is ready
✔ Installed GitOps Dashboard

# 확인
flux -n flux-system get helmrelease
NAME     	REVISION	SUSPENDED	READY	MESSAGE
ww-gitops	4.0.22  	False    	True 	Release reconciliation succeeded

kubectl -n flux-system get pod,svc

gitops 대쉬보드에 접근하기 위해 Ingress 설정을 해준다. 이후 도메인 주소에 연결하면 대쉬보드 설치 및 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"

github에 있는 nginx manifest를 k8s에 배포한다. 이때 샘플 소스는 악분님의 git repo을 사용했다.

# 소스 생성 : 유형 - 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
✚ generating GitRepository source
► applying GitRepository source
✔ GitRepository source created
◎ waiting for GitRepository source reconciliation
✔ GitRepository source reconciliation completed
✔ fetched revision: main@sha1:4478b54cb...

# 소스 확인
flux get sources git
NAME          	REVISION          	SUSPENDED	READY	MESSAGE
flux-system   	main@sha1:ede87212	False    	True 	stored artifact for revision 'main@sha1:ede87212'
nginx-example1	main@sha1:4478b54c	False    	True 	stored artifact for revision 'main@sha1:4478b54c'

kubectl -n flux-system get gitrepositories
NAME             URL                                                    AGE    READY   STATUS
flux-system      ssh://git@github.com/myname/fleet-infra                137m   True    stored artifact for revision 'main@sha1:ede8721252d...'
nginx-example1   https://github.com/sungwook-practice/fluxcd-test.git   102s   True    stored artifact for revision 'main@sha1:4478b54cb7a...'

flux 애플리케이션 생성한다. 유형(kustomization) , 깃 소스 경로( —path ./nginx) → gitops 웹 대시보드에서 확인을 진행했다.
gitops 대시보드에 nginx-example1이 생성된 것을 확인할 수 있었다.

# [터미널] 모니터링
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

이렇게 간단하게 Flux 테스트를 진행했으니 Flux 실습 리소스를 삭제한다.

# [터미널] 모니터링
watch -d kubectl get pod,svc nginx-example1

# flux 애플리케이션 삭제 >> 파드와 서비스는? flux 애플리케이션 생성 시 --prune 옵션 false(default 값)
flux delete kustomization nginx-example1
# Pod와 서비스는 삭제되지 않고 Application만 삭제 된다. --prune 옵션 false이기 때문에! 다만, gitops 대시보드에는 삭제 된 것으로 나타난다.
flux get kustomizations
NAME       	REVISION          	SUSPENDED	READY	MESSAGE
flux-system	main@sha1:ede87212	False    	True 	Applied revision: main@sha1:ede87212

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
NAME          	REVISION          	SUSPENDED	READY	MESSAGE
flux-system   	main@sha1:ede87212	False    	True 	Applied revision: main@sha1:ede87212
nginx-example1	main@sha1:4478b54c	False    	True 	Applied revision: main@sha1:4478b54c

kubectl get pod,svc nginx-example1

# flux 애플리케이션 삭제 >> 파드와 서비스는? 
flux delete kustomization nginx-example1
# Pod와 서비스 모두 삭제 된다. --prune 옵션이 true이기 때문에!
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

# flux 삭제

Flux에 대해 간단하게 다뤄봤는데 간편하다는 생각이 들었다. 가장 큰 장점은 k8s 클러스터와 Git 저장소 간의 동기화를 진행해주는 게 제일 큰 장점 같다.

3. GitOps with ArgoCD

ArgoCD는 지난 PKOS 스터디 때 다뤘던 내용이 있어 해당 내용을 참고해서 진행했다.

3-1. Harbor을 통해 Image 저장소 구축

Harbor : Docker 이미지를 저장하고 관리할 수 있는 중앙 집중식 이미지 저장소이다. Harbor을 통해 로컬 개발 환경에서 Docker 이미지를 빌드한 뒤 업로드할 수 있고 Docker CLI 및 API와 호환이 가능하다. 또한 이미지의 보안적 취약점 및 인증 문제를 확인할 수 있는 특징이 있다.

Harbor을 HelmChart을 통해 설치한다.
이때 values.yaml 파일의 일부분을 수정해야 하는데 아래 내용을 참고한다.

# 사용 리전의 인증서 ARN 확인
aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text
CERT_ARN=`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text`
echo "alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN"

# 하버 설치<
helm repo add harbor https://helm.goharbor.io
helm fetch harbor/harbor --untar --version 1.11.0
vim ~/harbor/values.yaml
----------------------
expose.tls.certSource=none                        # 19줄
expose.ingress.hosts.core=harbor.<각자자신의도메인>    # 36줄
expose.ingress.hosts.notary=notary.<각자자신의도메인>  # 37줄<
expose.ingress.hosts.core=harbor.bs-yang.com
expose.ingress.hosts.notary=notary.bs-yang.com
expose.ingress.controller=alb                      # 44줄
expose.ingress.className=alb                       # 47줄~
expose.ingress.annotations=alb.ingress.kubernetes.io/scheme: internet-facing
expose.ingress.annotations=alb.ingress.kubernetes.io/target-type: ip
expose.ingress.annotations=alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
expose.ingress.annotations=alb.ingress.kubernetes.io/certificate-arn: ${CERT_ARN}   # 각자 자신의 값으로 수정입력
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: ${CERT_ARN}   # 각자 자신의 값으로 수정입력
externalURL=https://harbor.<각자자신의도메인>          # 131줄
externalURL=https://harbor.bs-yang.com             
----------------------
kubectl create ns harbor

helm install harbor harbor/harbor -f ~/harbor/values.yaml --namespace harbor --version 1.11.0

values.yaml 파일에 넣은 도메인 주소로 접속해서 로그인을 진행한다.

로그인이 잘 됐다면 새 프로젝트를 만들어준다.

컨테이너 이미지에 Tag 설정을 한 뒤 Harbor Project에 업로드를 한다.

# 컨테이너 이미지 가져오기
docker pull nginx && docker pull busybox && docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        latest    f9c14fe76d50   11 days ago   143MB
busybox      latest    8135583d97fe   2 weeks ago   4.86MB

# 태그 설정
docker tag busybox harbor.$MyDomain/aews/busybox:0.1
docker image ls
REPOSITORY                        TAG       IMAGE ID       CREATED       SIZE
nginx                             latest    f9c14fe76d50   11 days ago   143MB
busybox                           latest    8135583d97fe   2 weeks ago   4.86MB
harbor.bs-yang.com/aews/busybox   0.1       8135583d97fe   2 weeks ago   4.86MB

# 로그인 - 비밀번호는 미리 Harbor Portal에서 변경을 한다. 아래는 기본값을 바탕으로 한다.
echo 'Harbor12345' > harborpw.txt
cat harborpw.txt | docker login harbor.$MyDomain -u admin --password-stdin
cat /root/.docker/config.json | jq

# 이미지 업로드
docker push harbor.$MyDomain/aews/busybox:0.1
The push refers to repository [harbor.bs-yang.com/aews/busybox]
9547b4c33213: Pushed
0.1: digest: sha256:5cd3db04b8be5773388576a83177aff4f40a03457a63855f4b9cbe30542b9a43 size: 528

프로젝트에 이미지가 잘 업로드 된 것을 확인했으니 업로드 된 이미지로 Deployment을 생성하는 과정을 테스트해본다.
샘플 yaml을 받은 뒤 이미지 위치를 내 Harbor Project 장소로 선택한다. 이렇게 하면 Pods을 배포할 때 위에서 설정한 이미지 저장소를 사용하게 된다. Pulling/Pulled을 참고하면 내 주소를 사용함을 알 수 있다.

# 파드 배포
curl -s -O https://raw.githubusercontent.com/junghoon2/kube-books/main/ch13/busybox-deploy.yml
sed -i "s|harbor.myweb.io/erp|harbor.$MyDomain/aews|g" busybox-deploy.yml
kubectl apply -f busybox-deploy.yml
NAME                      READY   STATUS    RESTARTS   AGE
busybox-7494977b8-bpgs7   1/1     Running   0          3s

업로드 된 이미지를 스캔하고 앞으로 업로드 될 이미지를 자동으로 스캔하게 하는 설정을 진행한다.
Harbor 대시보드에서 이미지를 선택 후 SCAN을 클릭한다. SCAN이 아직 진행되지 않았을 때는 Vulnerabilities에 Not Scanned로 표시 된다.

Scan이 완료 된 뒤에 문제가 없을 경우 No vulnerability로 표기됨을 알 수 있다.

아래는 앞으로 업로드(Push) 될 이미지들을 자동으로 Scan하는 방법이다.
Project을 선택한 뒤 Configuration을 선택하고 Automatically scan images on push을 체크하고 화면 하단의 SAVE을 클릭한다.

테스트를 위해 새로운 이미지를 push해본다.
nginx에 태그 설정을 하고 push을 했더니 바로 SCAN이 실행된 것을 확인할 수 있었다.

# 태그 설정
docker tag nginx harbor.$MyDomain/aews/nginx:0.1
docker image ls
REPOSITORY                        TAG       IMAGE ID       CREATED       SIZE
nginx                             latest    f9c14fe76d50   12 days ago   143MB
harbor.bs-yang.com/aews/nginx     0.1       f9c14fe76d50   12 days ago   143MB
busybox                           latest    8135583d97fe   2 weeks ago   4.86MB
harbor.bs-yang.com/aews/busybox   0.1       8135583d97fe   2 weeks ago   4.86MB

# 이미지 업로드
docker push harbor.$MyDomain/aews/nginx:0.1


이렇게 간단하게 Harbor 테스트를 마쳤다.

3-2. GitLab을 통해 Local Git 소스 저장소 구축

GitLab : Git Repo을 내부에서 관리할 수 있는 서비스이다. 앞서 위 실습에서 사용한 github의 Private 버전이라고 생각하면 편리하다.
이번 실습은 생성한 파일을 GitLab Repo에 업로드하는 것을 목표로 한다.

우선 gitlab 설치를 진행한다. 설치는 특별한 내용은 없고 values.yml을 내 도메인 환경에 맞춰 변경해준다. inress annotations 부분에 group.name을 설정해주면 생성되는 서비스들을 하나의 ALB에 연결할 수 있다.

# 모니터링
kubectl create ns gitlab
watch kubectl get pod,pvc,ingress -n gitlab

# 설치
echo $CERT_ARN
helm repo add gitlab https://charts.gitlab.io/
helm repo update
helm fetch gitlab/gitlab --untar --version 6.8.1
vim ~/gitlab/values.yaml
----------------------
global:
  hosts:
    domain: <각자자신의도메인>             # 52줄
    https: true

  ingress:                             # 66줄~
    configureCertmanager: false
    provider: aws
    class: alb
    annotations:
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: ip
      alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
      alb.ingress.kubernetes.io/certificate-arn: ${CERT_ARN}   # 각자 자신의 값으로 수정입력
      alb.ingress.kubernetes.io/success-codes: 200-399
      alb.ingress.kubernetes.io/group.name: "gitlab" # 이렇게 할 경우 4개의 Ingress을 하나의 ALB로 생성 가능<
    tls:                               # 79줄
      enabled: false
----------------------
helm install gitlab gitlab/gitlab -f ~/gitlab/values.yaml --set certmanager.install=false --set nginx-ingress.enabled=false --set prometheus.install=false --set gitlab-runner.install=false --namespace gitlab --version 6.8.4

배포를 완료했으면 배포 상황을 확인해본다.
추가로, gitlab은 설치해서 사용하는 거기 때문에 root 계정의 비밀번호를 확인해서 로그인해야 한다.

# 확인 - SubCharts
# gitlab-gitaly : 웹서비스 혹은 ssh 방식으로 진행되는 깃 제목, 브랜치, 태그 등의 깃 요청 등에 대한 작업을 담당
# gitlab-gitlab-shell : https 가 아닌 ssh 방식으로 깃 명령어 실행 시 해당 요청을 처리
# gitlab-kas : gitlab agent server
# gitlab-postgresql : 유저, 권한, 이슈 등 깃랩의 메타 데이터 정보가 저장
# gitlab-redis-master : 깃랩 작업 정보는 레디스 캐시 서버를 이용하여 처리
# gitlab-sidekiq-all-in-1-v2 : 레디스와 연동하여 작업 큐 처리 용도로 사용
# gitlab-webservice-default : 깃랩 웹 서비스를 처리
helm list -n gitlab
NAME  	NAMESPACE	REVISION	UPDATED                                	STATUS  	CHART       	APP VERSION
gitlab	gitlab   	1       	2023-06-06 21:08:33.473720611 +0900 KST	deployed	gitlab-6.8.4	15.8.4

kubectl get pod,pvc,ingress,deploy,sts -n gitlab
kubectl df-pv -n gitlab
kubectl get-all -n gitlab

# 웹 root 계정 암호 확인
kubectl get secrets -n gitlab gitlab-gitlab-initial-root-password --template={{.data.password}} | base64 -d ;echo

root 로그인은 가급적 사용하지 않는 게 좋기 때문에 별도의 admin 계정을 생성하고 해당 계정에 토큰값도 생성하여 터미널에서 로그인도 할 수 있도록 한다.
상단 메뉴 버튼을 클릭하고 “Admin”을 클릭하여 관리자 화면으로 이동한다.

좌측 메뉴에서 Users을 클릭하고 나오는 화면에서 “New User”을 클릭한다.

이름과 username은 사용자 임의로 입력하고 메일 주소도 입력해준다.
그리고 권한을 Regular이 아닌 Administraotr로 설정하고 하단의 “Create User”을 클릭한다.

사용자의 암호를 설정하기 위해 생성된 사용자의 화면에서 Edit을 클릭한다.

Password를 원하는 암호로 입력하고 하단의 Save Changes을 클릭한다. 이때 암호는 임시암호로 해당 사용자로 로그인하면 암호를 변경하라고 나오니 임의로 입력하도록 하자.

사용자가 생성되고 권한까지 부여가 완료됐으니 root 사용자는 로그아웃하고 해당 사용자(여기서는 bsyang)로 다시 로그인을 진행한다. 로그인 하게되면 암호를 변경하라고 나오니 암호를 변경한다.

사용자의 Token을 생성하기 위해 Admin->Users->생성 된 사용자(bsyang) 화면에서 “Impersonation Tokens”을 클릭한다.

로그인을 하고 나면 동일하게 Admin->Users->만든 사용자까지 들어간 뒤Token 이름을 입력하고 Expiration Date는 본인이 원하는 날짜까지로 선택한다. 권한은 우선 전체를 다 부여하고 하단의 Create Impersonation Token을 클릭한다.
그럼 바로 상단에 Token값이 나오는데 눈 모양 아이콘을 클릭해서 Token 값을 확인하고 옆에 복사 버튼을 클릭해서 안전한 곳에 붙여넣기 해둔다. (ex : glpat-95By……)

이제 프로젝트에 push을 하기 위해 프로젝트를 만든다. gitlab 메인화면에서 “Create a project”을 클릭하고 다음 화면에서 “Create blank project”을 클릭해서 빈 프로젝트를 생성하면 된다.
프로젝트 이름과 네임스페이스(gitlab user name)을 선택하고 Internal을 선택한 뒤 생성해준다.

생성한 Gitlab 프로젝트에 파일을 업로드(push)하기 위해 작업을 진행해준다.
git config로 계정 정보 설정을 해주고 git clone, push을 진행해준다. 이때 password에는 Gitlab 대시보드에 로그인할 때 쓰는 암호가 아닌 위에서 생성한 Token값을 입력해주면 된다.
그리고 로컬에 test.txt를 만들고 gitlab에 Push을 하면 대시보드에서 해당 파일을 확인할 수 있다. 파일까지 들어가서 내가 입력한 내용(gitlab test memo)이 잘 입력되었는지까지 확인을 해보자.

#
mkdir ~/gitlab-test && cd ~/gitlab-test

# git 계정 초기화 : 토큰 및 로그인 실패 시 매번 실행해주자
git config --system --unset credential.helper
git config --global --unset credential.helper

# git 계정 정보 확인 및 global 계정 정보 입력
git config --list
git config --global user.name "<각자 자신의 Gialba 계정>"
git config --global user.email "<각자 자신의 Gialba 계정의 이메일>"
git config --global user.name "myname"
git config --global user.email "mymail@mail.net"

# git clone
git clone https://gitlab.$MyDomain/<각자 자신의 Gitlab 계정>/test-stg.git
git clone https://gitlab.$MyDomain/myname/test-stg.git
Cloning into 'test-stg'...
Username for 'https://gitlab.bs-yang.com': bsyang
Password for 'https://bsyang@gitlab.bs-yang.com': (토근입력)
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.

# 이동
ls -al test-stg && cd test-stg && pwd
total 8
drwxr-xr-x 3 root root   35 Jun  7 13:20 .
drwxr-xr-x 3 root root   22 Jun  7 13:18 ..
drwxr-xr-x 8 root root  163 Jun  7 13:20 .git
-rw-r--r-- 1 root root 6207 Jun  7 13:20 README.md
/root/gitlab-test/test-stg

# 파일 생성 및 깃 업로드(push) : 웹에서 확인
echo "gitlab test memo" >> test.txt
git add . && git commit -m "initial commit - add test.txt"
git push
Username for 'https://gitlab.bs-yang.com': bsyang
Password for 'https://bsyang@gitlab.bs-yang.com': (토근입력)
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 2 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 299 bytes | 299.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To https://gitlab.bs-yang.com/bsyang/test-stg.git
   33958db..5075bf6  main -> main

이렇게 간단하게 gitlab 테스트를 진행해봤다. 이어서 ArgoCD을 진행해보도록 한다.

3-3. ArgoCD를 활용한 깃옵스(GitOps) 시스템 구축

Harbor을 통해 컨테이너 이미지 저장소를 구성했고 Gitlab으로 코드 저장소를 구성했다면 이제 ArgoCD을 통해 GitOps 시스템을 구축할 계획이다.
ArgoCD는 Kubernetes 클러스터 내에서 CI/CD (지속적인 통합 및 지속적인 배포)를 위한 도구이다. Argo CD는 GitOps 원칙에 기반을 둔 애플리케이션 전달 및 배포를 자동화하는 데 사용되고 애플리케이션 배포를 Git 리포지토리의 상태와 동기화한다. 따라서 애플리케이션의 배포 상태를 Git 저장소에 정의하고, Git 저장소의 변경 사항에 따라 배포를 업데이트할 수 있있고. 이를 통해 애플리케이션의 인프라 및 설정을 관리하고, 배포 프로세스를 자동화하며, 롤백 및 복구 기능을 제공한다.

ArgoCD을 통해 Application을 배포하는 로직은 아래와 같다.
ArgoCD CLI을 통해 ArgoCD에 명령을 내리면 GitLab에 Push 된 yaml 등을 활용해서 Application을 EKS에 배포한다.

ArgoCD 실습을 진행하기 위해 기존 PKOS 스터디 때는 helm chart로 설치하였는데 이번에는 argocd 측에서 제공하는 yaml을 사용하여 설치하고 Ingress 설정을 진행하였다.
이후 Login PW을 확인하고 웹 대시보드에 접속해서 로그인까지 완료했다.

# 모니터링 [터미널2]
kubectl create ns argocd
watch kubectl get pod,pvc,svc -n argocd

# 설치
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# 설치 확인
# argocd-application-controller : 실행 중인 k8s 애플리케이션의 설정과 깃 저장소의 소스 파일에 선언된 상태를 서로 비교하는 컨트롤러. 상태와 다르면 ‘OutOfSync’ 에러를 출력.
# argocd-dex-server : 외부 사용자의 LDAP 인증에 Dex 서버를 사용할 수 있음
# argocd-repo-server : 원격 깃 저장소의 소스 코드를 아르고시디 내부 캐시 서버에 저장합니다. 디렉토리 경로, 소스, 헬름 차트 등이 저장.
kubectl get pod,pvc,svc,deploy,sts -n argocd
kubectl get-all -n argocd

kubectl get crd | grep argoproj

# 서비스 노출을 위해 서비스 타임 NodePort로 변경
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "NodePort"}}'

# Ingress 설정 및 설치
CERT_ARN=`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text`
echo $CERT_ARN

cat <<EOF > argocd-ingress-set.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-ingress
  namespace: argocd
  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: $CERT_ARN
    alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
    alb.ingress.kubernetes.io/backend-protocol: HTTPS
    alb.ingress.kubernetes.io/healthcheck-path: /login
spec:
  rules:
    - host: argocd.$MyDomain
      http:
        paths:
          - pathType: Prefix
            path: /
            backend:
              service:
                name: argocd-server
                port:
                  number: 443
EOF
kubectl apply -f argocd-ingress-set.yaml -n argocd

# ingress 설정 확인
kubectl get pod,pvc,svc,deploy,sts,ingress -n argocd

# admin 계정의 암호 확인
ARGOPW=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)
echo $ARGOPW

# 웹 접속 로그인 (admin) CLB의 DNS 주소로 접속
echo -e "Argocd Web URL = https://argocd.$MyDomain"

로그인을 진행했으니 앞서 생성한 Gitlab과 k8s cluster 등록을 위해 ArgocdCLI 도구를 설치한다.

# 최신버전 설치
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
chmod +x /usr/local/bin/argocd

# 버전 확인
argocd version --short
argocd: v2.7.4+a33baa3
FATA[0000] Argo CD server address unspecified

# Help
# argocd app : 쿠버네티스 애플리케이션 동기화 상태 확인
# argocd context : 복수의 쿠버네티스 클러스터 등록 및 선택
# argocd login : 아르고시디 서버에 로그인 
# argocd repo : 원격 깃 저장소를 등록하고 현황 파악
argocd

# argocd 서버 로그인
argocd login argocd.$MyDomain --username admin --password $ARGOPW
'admin:login' logged in successfully
Context 'argocd.bs-yang.com' updated

# 기 설치한 깃랩의 프로젝트 URL 을 argocd 깃 리포지토리(argocd repo)로 등록. 깃랩은 프로젝트 단위로 소스 코드를 보관.
argocd repo add https://gitlab.$MyDomain/myname/test-stg.git --username myname --password <깃랩 계정 암호>
Repository 'https://gitlab.bs-yang.com/myname/test-stg.git' added

# gitlab 등록 확인
argocd repo list
TYPE  NAME  REPO                                            INSECURE  OCI    LFS    CREDS  STATUS      MESSAGE  PROJECT
git         https://gitlab.bs-yang.com/myname/test-stg.git  false     false  false  true   Successful

Git repo가 등록됐으니 해당 repo을 사용해서 RabbitMQ Application을 배포해본다.
yaml 배포를 입력하고 argocd UI을 확인하면 바로 새로운 RabbitMQ Application이 뜨는 것을 확인할 수 있다.
배포되면 처음엔 OutOfSync/Missing 상태인데 이때 Sync을 진행해주면 상태가 변경 된다.

# test-stg 깃 디렉터리에서 아래 실행
cd ~/gitlab-test/test-stg

# 깃 원격 오리진 주소 확인
git config -l | grep remote.origin.url
remote.origin.url=https://gitlab.bs-yang.com/myname/test-stg.git

# RabbitMQ 헬름 차트 설치
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm fetch bitnami/rabbitmq --untar --version 11.10.3
cd rabbitmq/
cp values.yaml my-values.yaml

# 헬름 차트를 깃랩 저장소에 업로드
git add . && git commit -m "add rabbitmq helm"
git push
Username for 'https://gitlab.bs-yang.com': myname
Password for 'https://bsyang@gitlab.bs-yang.com':
Enumerating objects: 57, done.
Counting objects: 100% (57/57), done.
Delta compression using up to 16 threads
Compressing objects: 100% (54/54), done.
Writing objects: 100% (56/56), 65.14 KiB | 6.51 MiB/s, done.
Total 56 (delta 13), reused 0 (delta 0), pack-reused 0
To https://gitlab.bs-yang.com/bsyang/test-stg.git
   856ff34..c96d6cb  main -> main

# 수정
cd ~/
curl -s -O https://raw.githubusercontent.com/wikibook/kubepractice/main/ch15/rabbitmq-helm-argo-application.yml
vim rabbitmq-helm-argo-application.yml
--------------------------------------
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: rabbitmq-helm
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  destination:
    namespace: rabbitmq
    server: https://kubernetes.default.svc
  project: default
  source:
    repoURL: https://gitlab.myurl.com/path/xxx.git #내 주소로 변경
    path: rabbitmq
    targetRevision: HEAD
    helm:
      valueFiles:
      - my-values.yaml
  syncPolicy:
    syncOptions:
    - CreateNamespace=true
--------------------------------------

# 모니터링 : argocd 웹 화면 보고 있기!
echo -e "Argocd Web URL = https://argocd.$MyDomain"

# 배포
kubectl apply -f rabbitmq-helm-argo-application.yml

# yaml 파일 배포 후 상태 확인 (OutOfSync 상태)
kubectl get applications.argoproj.io -n argocd
NAME            SYNC STATUS   HEALTH STATUS
rabbitmq-helm   OutOfSync     Missing

# sync 후 상태 확인
NAME            SYNC STATUS   HEALTH STATUS
rabbitmq-helm   Synced        Healthy

해당 Application을 클릭해서 들어가면 추가로 svc, ep 등이 확장되었음도 확인할 수 있다.

위 화면의 pod는 현재 1개인데 이를 2개로 확장하는 명령어를 입력해본 뒤 화면이 어떻게 변화하는지 확인해봤다.
Replicas를 2개로 변경하는 명령어를 내리면 Pod가 하나 더 생기면서 sts와 Application의 상태가 OutOfSync 상태로 변경되는 것을 알 수 있다.
이는 Pod가 들어있는 sts와 상위의 Application의 정보가 변경되었음을 의미한다.

# sts 파드 1개에서 2개로 증가 설정 후 argocd 웹 화면 모니터링
kubectl scale statefulset -n rabbitmq rabbitmq-helm --replicas 2

OutOfSync 문제를 해결하기 위해 다시 Sync을 진행해준다.
모두 Synced 상태로 변경되는 것을 확인할 수 있다.

ArgoCD을 활용해서 Gitlab에 있는 yaml 파일을 통해 Application을 배포해보았다. 공동작업을 할 경우 yaml을 gitlab에 올려놓고 작업을 하게되는데 그럴 때 저장소를 통해 배포를 진행할 수 있어서 좋은 방안이라고 생각한다.

4. Crossplane

Crossplane은 k8s Native Infrastructure Cross Cloud Control System이다. 즉, Crossplane을 사용하여 k8s 클러스터에서 멀티 클라우드 및 온프레미스 환경에서 인프라 소스를 프로비저닝하고 관리할 수 있게 된다.
k8s CRD을 사용하여 인프라 리소스를 정의하고 제어할 수 있는데 앞서 다룬 ACK와 유사하다고 볼 수 있다. 오히려 다양한 Provider을 제공하기 때문에 ACK보다 조금 더 확장 된 서비스라고 할 수 있다.

https://docs.crossplane.io/v1.12/getting-started/introduction/

Crossplane을 테스트하기 위해 설치를 우선 진행한다.

# helm chart 통한 설치
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
kubectl create namespace  crossplane-system
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane

# 설치 확인
kubectl get all -n crossplane-system
NAME                                           READY   STATUS    RESTARTS   AGE
pod/crossplane-9f6d5cd7b-5x9np                 1/1     Running   0          64s
pod/crossplane-rbac-manager-699dc89cf4-vck8n   1/1     Running   0          64s

NAME                          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/crossplane-webhooks   ClusterIP   10.100.175.230   <none>        9443/TCP   64s

NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/crossplane                1/1     1            1           64s
deployment.apps/crossplane-rbac-manager   1/1     1            1           64s

NAME                                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/crossplane-9f6d5cd7b                 1         1         1       64s
replicaset.apps/crossplane-rbac-manager-699dc89cf4   1         1         1       64s

# crossplane CLI 설치
curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh
sudo mv kubectl-crossplane /usr/local/bin
kubectl crossplane --help

# AWS Provider 설치
cat <<EOF | kubectl apply -f -
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: upbound-provider-aws
spec:
  package: xpkg.upbound.io/upbound/provider-aws:v0.27.0
EOF

# provider 확인. HEALTHY가 true가 되는데까지 최대 약 5분 정도 소요
kubectl get providers
NAME                   INSTALLED   HEALTHY   PACKAGE                                        AGE
upbound-provider-aws   True        True      xpkg.upbound.io/upbound/provider-aws:v0.27.0   98s

AWS Provider에서 특정 사용자 권한을 가져다가 사용하기 위해 Provider 구성을 진행한다.
기존에 생성했던 administratoraccess 권한이 있는 사용자의 Access Profile를 별도의 txt 파일에 저장하고 해당 Profile을 기반으로 Secret을 만들고 Secret을 갖고 Provider에 구성을 진행한다.

# AWS Configure 정보를 토대로 aws-credentials.txt 파일 생성
[default]
aws_access_key_id = AKIAYRMZ...
aws_secret_access_key = DcWBPj3t...

# secret 만들기
kubectl create secret generic aws-secret -n crossplane-system --from-file=creds=./aws-credentials.txt
secret/aws-secret created

# Secret 정보 확인
kubectl describe secret -n crossplane-system
Name:         aws-secret
Namespace:    crossplane-system
...
Data
====
creds:  116 bytes
...

# Provider Config 파일 생성 수 업데이트 진행
cat <<EOF | kubectl apply -f -
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
  name: default
spec:
  credentials:
    source: Secret
    secretRef:
      namespace: crossplane-system
      name: aws-secret
      key: creds
EOF
providerconfig.aws.upbound.io/default created

# Provider Config 확인
kubectl describe providerconfig.aws.upbound.io/default -n crossplane-system
Name:         default
...
Spec:
  Credentials:
    Secret Ref:
      Key:        creds
      Name:       aws-secret
      Namespace:  crossplane-system
    Source:       Secret
...

Provider 구성까지 끝났으니 간단한 테스트로 S3을 배포해보도록 한다.
bucket 이름을 랜덤으로 생성한 후 crossplane API을 사용해서 S3 Bucket을 만드는 과정이다.
Bucket을 배포하기 전에 get buckets 명령어를 입력하면 리소스가 없다고 나온다. aws cli을 통해 확인하면 3개의 S3 Bucket이 나오는 것과는 대조적이다. 이는 k8s에서 동기화되지 않았기 때문이다. ACK에서도 동일하게 해당 내용처럼 진행되는 것을 알 수 있다.
배포 후 READY/SYNCED,가 모두 True가 될 때까지 기다린다.

# S3 목록 확인
aws s3 ls
2023-06-07 15:26:53 cf-templates-2awcm82lq9tn-ap-northeast-2
2023-04-27 13:20:41 cloudtrail-awslogs-....-not-delete
2023-04-29 14:28:11 do-not-delete-....

kubectl get buckets
No resources found

# S3 Bucket 생성
bucket=$(echo "crossplane-bucket-"$(head -n 4096 /dev/urandom | openssl sha1 | tail -c 10))
cat <<EOF | kubectl apply -f -
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
  name: $bucket
spec:
  forProvider:
    region: $AWS_REGION
  providerConfigRef:
    name: default
EOF
bucket.s3.aws.upbound.io/crossplane-bucket-9582936ef created

# S3 Bucket 생성 확인
kubectl get buckets
NAME                          READY   SYNCED   EXTERNAL-NAME                 AGE
crossplane-bucket-9582936ef   True    True     crossplane-bucket-9582936ef   14s

aws s3 ls
2023-06-07 15:26:53 cf-templates-2awc...
2023-04-27 13:20:41 cloudtrail-awslogs-...
2023-06-08 22:39:56 crossplane-bucket-9582936ef
2023-04-29 14:28:11 do-not-delete-...

S3 Bucket을 배포해봤으니 기존 AWS에 배포한 S3 Bucket을 crossplane에 Import 하는 내용을 진행해본다.
AWS Console에서 S3 Bucket을 먼저 생성하고 해당 Bucket을 Crossplane을 통해 Import하는 과정으로 진행한다.
bucket import 후 확인하니 문제 없이 S3 Bucket이 Import 된 것을 볼 수 있었다.

# 앞서 crossplane에서 만든 bucket과 AWS Console에서 만든 Bucket(test 붙은 bucket) 목록 확인
aws s3 ls
...
2023-06-08 22:48:45 crossplane-bucket-64d76c5fa-test
2023-06-08 22:39:56 crossplane-bucket-9582936ef

# crossplane bucket 목록 확인. 앞서 Crossplane 통해 만든 Bucket만 보임
kubectl get bucket
NAME                          READY   SYNCED   EXTERNAL-NAME                 AGE
crossplane-bucket-9582936ef   True    True     crossplane-bucket-9582936ef   10m

# bucket import 진행
cat <<EOF | kubectl apply -f -
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
  name: bucket-import
  annotations:
    crossplane.io/external-name: crossplane-bucket-64d76c5fa-test
spec:
  forProvider:
    region: $AWS_REGION
  providerConfigRef:
    name: default
EOF
bucket.s3.aws.upbound.io/bucket-import created

# bucket import 확인
kubectl get bucket
NAME                          READY   SYNCED   EXTERNAL-NAME                      AGE
bucket-import                 True    True     crossplane-bucket-64d76c5fa-test   76s
crossplane-bucket-9582936ef   True    True     crossplane-bucket-9582936ef        18m

S3 Bucket Create&Import을 했으니 이제 Delete을 해보도록 한다.
AWS Console에서 만들고 Import한 Bucket과 Crossplane에서 생성한 Bucket 모두 각각 삭제를 진행해보았다.
삭제는 문제없이 진행되었고 AWS CLI와 Crossplane CLI에서 모두 Bucket이 지워진 것으로 보인다. 물론 AWS Console에서도 동일하게 삭제 된 것으로 나타난다.

# 삭제 전 Bucket 목록 확인
aws s3 ls
2023-06-08 22:57:22 crossplane-bucket-64d76c5fa-test
2023-06-08 22:39:56 crossplane-bucket-9582936ef

kubectl get bucket
NAME                          READY   SYNCED   EXTERNAL-NAME                      AGE
bucket-import                 True    True     crossplane-bucket-64d76c5fa-test   5m16s
crossplane-bucket-9582936ef   True    True     crossplane-bucket-9582936ef        22m

# crossplane으로 만든 Bucket 삭제
kubectl delete bucket crossplane-bucket-9582936ef
bucket.s3.aws.upbound.io "crossplane-bucket-9582936ef" deleted

kubectl get bucket
NAME            READY   SYNCED   EXTERNAL-NAME                      AGE
bucket-import   True    True     crossplane-bucket-64d76c5fa-test   6m4s

aws s3 ls
2023-06-08 22:57:22 crossplane-bucket-64d76c5fa-test

# AWS Console에서 만들고 crossplane으로 Import한 Bucket 삭제
kubectl delete bucket bucket-import
bucket.s3.aws.upbound.io "bucket-import" deleted

kubectl get bucket
No resources found

aws s3 ls

Crossplane을 간단하게 사용해봤는데 ACK보다 동기화 속도도 빠르고 더 간편하게 작동하는 것 같다. 추후에 k8s에서 AWS 서비스들을 관리해야 하는 순간이 온다면 나는 ACK보다는 crossplane을 사용할 것 같다.

5. eksdemo

eksdemo는 EKS을 사용해서 k8s 클러스터를 배포하고 관리하는데 도움을 주는 예제 및 데모 Application으로 k8s 기반의 Application 배포 및 관리를 위한 다양한 기능과 리소스를 제공한다. Eksdemo는 다음과 같은 주요 기능과 컴포넌트를 갖고 있다.

  1. 애플리케이션 샘플: Eksdemo는 Kubernetes 클러스터에서 실행되는 예제 애플리케이션을 제공한다. 이 애플리케이션은 다양한 마이크로서비스로 구성되어 있으며, 컨테이너화된 애플리케이션 배포와 관리에 대한 실제 시나리오를 보여준다.
  2. 클러스터 구성: Eksdemo는 EKS 클러스터를 배포하기 위한 구성 파일과 스크립트를 제공한다. 이를 통해 클러스터의 크기, 노드 인스턴스 유형, 스토리지 옵션 등을 구성할 수 있다.
  3. CI/CD 지원: Eksdemo는 CI/CD (Continuous Integration/Continuous Deployment) 워크플로를 구축하기 위한 기능과 도구를 포함한다. 예를 들어, GitHub Actions, AWS CodePipeline 등을 사용하여 애플리케이션 배포를 자동화할 수 있다.
  4. 서비스 디스커버리: Eksdemo는 Kubernetes 내부에서 서비스 디스커버리를 구성하는 방법과 관련된 리소스를 제공한다. 이를 통해 서비스 간의 통신과 로드 밸런싱을 구현할 수 있다.
  5. 로깅 및 모니터링: Eksdemo는 Amazon CloudWatch, Prometheus, Grafana 등과 같은 로깅 및 모니터링 도구를 사용하여 클러스터의 상태와 애플리케이션 성능을 모니터링하는 방법을 안내한다.

eksdemo을 사용해보기 위해 eksdemo을 먼저 설치해봤다.
설치는 github에서 압축파일을 당누로드 받고 압축을 풀어서 /usr/local/bin으로 파일을 이동하면서 간단하게 마무리 됐다.

# 압축파일 다운로드
curl -sSL -o eksdemo_Linux_x86_64.tar.gz https://github.com/awslabs/eksdemo/releases/download/v0.8.0/eksdemo_Linux_x86_64.tar.gz

# 압축해제 및 파일 이동
tar xzvf eksdemo_Linux_x86_64.tar.gz
mv eksdemo /usr/local/bin/

ls /usr/local/bin
argocd  aws  aws_completer  eksctl  eksdemo  helm  kubectl  yh

# 설치 확인
eksdemo version
eksdemo version info: cmd.Version{Version:"0.8.0", Date:"2023-06-03T17:45:05Z", Commit:"bac7ddb"}

eksdemo을 설치했으니 간단하게 내 k8s cluster을 잘 불러오는지 조회를 해보았다.
기본적으로 eksctl이 설치되어 있어야 하는 전제조건이 있기 때문에 따로 리전이 다르지 않다면 aws configure에 저장 된 IAM User 정보와 eksctl 정보를 통해 cluster 정보를 받아올 수 있다.

# cluster 정보 조회
eksdemo get cluster
+-------+--------+---------+---------+----------+----------+
|  Age  | Status | Cluster | Version | Platform | Endpoint |
+-------+--------+---------+---------+----------+----------+
| 1 day | ACTIVE | *myeks  |    1.24 | eks.7    | Public   |
+-------+--------+---------+---------+----------+----------+
* Indicates current context in local kubeconfig

# node 정보 조회
eksdemo get node -c $CLUSTER_NAME
+-------+--------------------+---------------------+------------+-----------------+-----------+
|  Age  |        Name        |     Instance Id     |    Type    |      Zone       | Nodegroup |
+-------+--------------------+---------------------+------------+-----------------+-----------+
| 1 day | ip-192-168-1-43.*  | i-0d9e7f20ce15ed4e6 | c5.4xlarge | ap-northeast-2a | ng1       |
| 1 day | ip-192-168-2-192.* | i-023ba2a4ccacca991 | c5.4xlarge | ap-northeast-2b | ng1       |
| 1 day | ip-192-168-3-73.*  | i-0a85a3af6d6bf863d | c5.4xlarge | ap-northeast-2c | ng1       |
+-------+--------------------+---------------------+------------+-----------------+-----------+
* Names end with "ap-northeast-2.compute.internal"

간단하게 조회를 했으니 eksdemo을 통해 Application을 배포해보도록 한다.
ACM에 있는 인증서 정보를 가져와서 해당 인증서를 사용하는 TLS 연결하는 Game-2048 Application을 배포할 계획이다.

클러스터 이름과 Ingress 정보를 입력하고 dry-run을 진행하면 아주 빠른 속도로 manifest 파일을 생성해준다.
파일을 잘 확인해본다. 직접 yaml을 작성하는 것과 유사하게 깔끔하게 만들어주는 것을 확인할 수 있다.

# dry run 통해 manifest 정보 확인
eksdemo install example-game-2048 -c $CLUSTER_NAME -I game2048.$MyDomain --dry-run
Manifest Installer Dry Run:
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: game-2048
  name: deployment-2048
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: app-2048
  replicas: 1
  template:
    metadata:
      labels:
        app.kubernetes.io/name: app-2048
    spec:
      containers:
      - image: public.ecr.aws/l6m2t8p7/docker-2048:latest
        imagePullPolicy: Always
        name: app-2048
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: game-2048
  name: service-2048
  annotations:
    {}
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: ClusterIP
  selector:
    app.kubernetes.io/name: app-2048
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: game-2048
  name: ingress-2048
  annotations:
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/ssl-redirect: '443'
    alb.ingress.kubernetes.io/target-type: ip
spec:
  ingressClassName: alb
  rules:
    - host: game2048.bs-yang.com
      http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: service-2048
              port:
                number: 80
  tls:
  - hosts:
    - game2048.bs-yang.com

manifest을 확인했다면 설치를 진행하고 설치되면서 생성되는 Application, ALB 정보를 확인해보았다.
eksdemo에서 뿐만 아니라 kubectl 에서 조회할 때도 제대로 나오는 것을 확인할 수 있었다.

# game2048 설치
eksdemo install example-game-2048 -c $CLUSTER_NAME -I game2048.$MyDomain
Helm installing...
2023/06/08 16:31:40 creating 1 resource(s)
2023/06/08 16:31:40 creating 3 resource(s)
Using chart version "n/a", installed "example-game-2048" version "latest" in namespace "game-2048"

# Application 확인
eksdemo get application -c $CLUSTER_NAME
+------------------------------+-------------+---------+----------+--------+
|             Name             |  Namespace  | Version |  Status  | Chart  |
+------------------------------+-------------+---------+----------+--------+
| aws-load-balancer-controller | kube-system | v2.5.2  | deployed | 1.5.3  |
| example-game-2048            | game-2048   | latest  | deployed | n/a    |
| gitlab                       | gitlab      | 15.8.4  | deployed | 6.8.4  |
| harbor                       | harbor      | 2.7.0   | deployed | 1.11.0 |
+------------------------------+-------------+---------+----------+--------+

# ALB 확인 Provisioning -> active가 될 때까지 대기
eksdemo get load-balancer -c $CLUSTER_NAME
+------------+--------------+----------------------------------+------+-------+-----+-----+
|    Age     |    State     |               Name               | Type | Stack | AZs | SGs |
+------------+--------------+----------------------------------+------+-------+-----+-----+
| 43 seconds | provisioning | k8s-game2048-ingress2-70d50ce3fd | ALB  | ipv4  |   3 |   2 |
| 23 hours   | active       | k8s-harbor-harborin-2352dee8a2   | ALB  | ipv4  |   3 |   2 |
| 23 hours   | active       | k8s-gitlab-536957cc0a            | ALB  | ipv4  |   3 |   2 |
| 7 hours    | active       | k8s-argocd-argocdin-cc87c24740   | ALB  | ipv4  |   3 |   2 |
| 23 hours   | active       | k8s-harbor-harborin-b768b16202   | ALB  | ipv4  |   3 |   2 |
+------------+--------------+----------------------------------+------+-------+-----+-----+
* Indicates internal load balancer

# kubectl 통해 확인
kubectl get pod,pvc,svc,deploy,sts,ingress -n game-2048
NAME                                   READY   STATUS    RESTARTS   AGE
pod/deployment-2048-6bc9fd6bf5-nqg7k   1/1     Running   0          11m

NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/service-2048   ClusterIP   10.100.55.172   <none>        80/TCP    11m

NAME                              READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/deployment-2048   1/1     1            1           11m

NAME                                     CLASS   HOSTS                  ADDRESS                                                                       PORTS     AGE
ingress.networking.k8s.io/ingress-2048   alb     game2048.bs-yang.com   k8s-game2048-ingress2-70d50ce3fd-740064416.ap-northeast-2.elb.amazonaws.com   80, 443   11m


# 사이트 주소 출력
echo "Game2048 URL : https://game2048.$MyDomain"
Game2048 URL : https://game2048.bs-yang.com

출력 된 URL로 접속하면 HTTPS 통한 game2048에 접속이 된 것을 확인할 수 있다.

eksdemo로 AWS 리소스 정보를 확인하고 간단하게 application 배포도 할 수 있는 것을 확인하였다.

6. 정리

이번 실습은 PKOS 스터디의 gitops 때처럼 설치해야 하는 서비스들이 많아서 애를 먹었다. 설치가 잘 안 되거나 기존에 CLB로 설치했던 부분이 있어 그걸 ALB로 변경하려던 과정에서 막히는 부분들이 있어 시간을 꽤 잡아먹은 것 같다.
harbor, gitlab, ArgoCD와 Flux 등을 테스트해보면서 간단하게 GitOps 환경을 구성할 수 있다는 것을 알게 됐다. 물론 Advanced 하게 사용하는 것은 아직 어렵겠지만 간단한 환경 구성은 할 수 있게 된 것 같다.

마지막 스터디 시간이었는데 여러가지를 해볼 수 있어서 좋았고 스터디에서 제공 된 내용 뿐 아니라 내가 직접 찾아서 해보는 내용들을 통해 EKS에 대해서 조금은 더 익숙해진 것 같다.
7주라는 시간 동안 스터디 준비와 진행에 힘써주신 가시다님께 감사 인사를 드리며 AWS EKS 스터디 내용 정리를 마친다.