-
EKS AWS의 관리형 쿠버네티스 서비스입니다. 마스터 노드가 포함되어있어 클러스터당 한달에 70 달러의 기본 요금이 책정되었습니다. 관리형 서비스이기 떄문에 API 엔드포인트만 제공하고 마스터노드로의 접근과 관리를 제한하고 있습니다. 예전에 외부 프로젝트를 진행하면서 잠시 AKS (마이크로 소프트 애저 쿠버네티스 서비스) 를 사용했었는데 그때 쿠버네티스의 강력함을 체감했습니다. 서비스에대한 디스커버리나 서비스에 맞게 노드나 컨테이너를 패킹하고 이모든 부분을 개발 문서 작성하듯이 작성하고 프로비저닝 할수 있다는점과, 개발 환경이나 프로덕트를 빠르게 구성하고 변경할 수 있다는 점에서 쿠버네티스는 개발자가 사용해야 하는구나를 많이 느꼈습니다.
저는 보통 프로젝트 단위로 작업을 시작하는데 프로젝트가 시작되면 인프라에서부터 프론트 영역까지의 모든 부분을 설계 해야 하는일이 종종 있었습니다. 고객사 별로 요구사항이 조금씩 다르긴 하지만 거의 대부분을 클라우드환경에서 작업을 했습니다. 그런 경우.. 인프라를 구성하고 안정적으로 만들기위해 시간이 많이 들게 되더라구요. 물리적으로 필요한 시간이라고 생각했습니다. 컨테이너 기반이어도 마찬가지였습니다. 배포를 위한 저장소, 저장소에서 배포까지의 파이프라인, 서비스를 실행해주는 러너, 러너를 모니터링.. 해야할 일이 많았습니다. 저는 프로젝트 설계와 매니징도 해야하지만 실제 코드를 짜는 실무도 해야하기 때문에 이러한 시간을 좀 줄이고자 하는 욕구가 강했습니다.
그러면서 맨 처음 ECS를 찾아 사용해봤는데요. 반 서버리스(?) 다 보니까 서버를 직접 세팅할 일도 없고 테라폼이나 AWS콘솔에서 설정만 해주면 배포판에 맞춰서 서비스를 교체해주는게 좋았습니다. 하지만 결국 ECS도 AWS의 서비스 이다 보니까 배포판을 교체하거나 롤아웃 롤백등의 경우에서 매끄럽지 못한 경우를 많이 겪게 되었습니다.
그래서 결국 EKS 를 사용해보기로 결심했고 나름대로 스터디를 해가며 실제 서비스에 적용해 보았습니다. 서론이 길었지만 아래 내용은 테라폼과 EKS 서비스를 통해 클러스터를 구성하는 과정입니다.
EKS 는 루트 계정이어도 클러스터의 내용이나 설정을 변경할 권한이 없습니다. 또한 기존 VPC를 이용해서 클러스터를 구성하려다가 권한 문제로 새 VPC 를 생성하여 다시 작업을 하게 되었습니다. 그래서 Cluster 를 운영할 IAM 계정을 하나 생성해주시고, 액세스 키와 시크릿 액세스 키를 미리 복사해 주세요. 콘솔에서 작업하지 않기때문에 프로그래밍 방식으로만 생성해 주어도 충분합니다.
Terraform
저는 테라폼이 너무 신세계였습니다. 프로젝트 단위로 작업을 하다보니 비슷한 스펙의 인프라를 구성해야하는 경우가 많이 있는데 테라폼은 그저 스크립트만 작성해놓으면 인프라를 거의 자동으로 생성해주니 시간적으로 여유가 많이 생겼습니다.
오픈소스이며 코드로 인프라 스트럭처를 관리한다는 개념으로 IaC라고도 하고 AWS에는 클라우드 포메이션이라는 IaC서비스도 있지만 테라폼은 아마존과 애저, 구글 클라우드 등과 같은 클라우드 서비스를 비롯하여 다양한 클라우드 서비스들을 프로비저닝 할 수 있게 도와줍니다.
https://www.terraform.io/downloads
저는 윈도우11 환경에서 테라폼을 이용했습니다. 다운로드 받으면 terraform.exe 파일만 덩그러니 있는데요 적절한 디렉토리로 옮기셔서 윈도우 환경 변수를 등록해 cmd 나 파워쉘에서 찾을수 있도록 설정해 주세요.
AWS CLI 설치
aws 리소스와 eks 관련 서비스를 세팅하기 위해 awscli 를 설치해주어야 합니다. 아래 링크에서 각 환경별 설치 지침에 따라 설치해주세요. 최종적으로 콘솔환경에서 aws 명령이 잘 실행 되면 됩니다.
https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html
AWS CLI 에 클러스터 루트용 IAM 계정으로 세팅을 해주세요.
PS C:\Users\juhojeong> aws configure AWS Access Key ID [****************PA4H]: ## ACCESS_KEY_ID 를 넣어준다. AWS Secret Access Key [****************6e4c]: ## SECRET_ACCESS_KEY 를 넣어준다. Default region name [ap-northeast-2]: ## 리전명 Default output format [None]:
Kubectl 설치
kubectl 은 콘솔에서 바로 쿠버네티스와 소통할수 있는 인터페이스 입니다. 앞으로 자주 접하게 될 명령어 이기도 합니다. https://subicura.com/k8s/prepare/kubectl-setup.html#%E1%84%89%E1%85%A5%E1%86%AF%E1%84%8E%E1%85%B5%E1%84%92%E1%85%A1%E1%84%80%E1%85%B5
eksctl 설치
EKS 는 AWS 서비스 이므로 EKS 에 관련된 설정을 하기위해 별도로 마련된 콘솔 프로그램입니다. https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/getting-started-eksctl.html
Terraform 리소스 설정
기본 프로그램을 모두 설치했다면 aws 리소스를 생성하기 위해 terraform 파일을 만들어 줍니다. 깃허브에 테라폼 aws 프로바이더 템플릿이 있으므로 이것을 이용하여 작성하도록 하겠습니다.
git clone https://github.com/terraform-providers/terraform-provider-aws.git cd terraform-provider-aws/examples/eks-getting-started
기본 terraform 프로바이더를 살펴보도록 하겠습니다. 파일 탐색기를 통해 해당 디렉토리로 접근해 주세요. 여기서 생성하는 리소스의 별칭을 my-terraform 이라고 하겠습니다.아래 파일에서 만약 이름을 다르게 하고 싶으시다면 my-terraform 에 해당하는 텍스트를 변경해 주세요.
eks-cluster.tf
resource "aws_iam_role" "my-terraform-cluster" { name = "terraform-eks-my-terraform-cluster" assume_role_policy = <<POLICY { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "eks.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } POLICY } resource "aws_iam_role_policy_attachment" "my-terraform-cluster-AmazonEKSClusterPolicy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" role = aws_iam_role.my-terraform.name } resource "aws_iam_role_policy_attachment" "my-terraform-cluster-AmazonEKSVPCResourceController" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSVPCResourceController" role = aws_iam_role.my-terraform.name } resource "aws_security_group" "my-terraform-cluster" { name = "terraform-eks-my-terraform-cluster" description = "Cluster communication with worker nodes" vpc_id = aws_vpc.my-terraform.id egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "my-terraform-eks" } } resource "aws_security_group_rule" "my-terraform-cluster-ingress-workstation-https" { cidr_blocks = [local.workstation-external-cidr] description = "Allow workstation to communicate with the cluster API Server" from_port = 443 protocol = "tcp" security_group_id = aws_security_group.my-terraform-cluster.id to_port = 443 type = "ingress" } resource "aws_eks_cluster" "osc" { name = var.my-terraform-cluster role_arn = aws_iam_role.osc-cluster.arn vpc_config { security_group_ids = [aws_security_group.my-terraform-cluster.id] subnet_ids = aws_subnet.my-terraform[*].id } depends_on = [ aws_iam_role_policy_attachment.my-terraform-cluster-AmazonEKSClusterPolicy, aws_iam_role_policy_attachment.my-terraform-cluster-AmazonEKSVPCResourceController, ] }
eks-worker-nodes.tf
resource "aws_iam_role" "my-terraform-node" { name = "terraform-eks-my-terraform-node" assume_role_policy = <<POLICY { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } POLICY } resource "aws_iam_role_policy_attachment" "my-terraform-node-AmazonEKSWorkerNodePolicy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy" role = aws_iam_role.my-terraform-node.name } resource "aws_iam_role_policy_attachment" "my-terraform-node-AmazonEKS_CNI_Policy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" role = aws_iam_role.my-terraform-node.name } resource "aws_iam_role_policy_attachment" "my-terraform-node-AmazonEC2ContainerRegistryReadOnly" { policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" role = aws_iam_role.my-terraform-node.name } resource "aws_eks_node_group" "my-terraform" { cluster_name = aws_eks_cluster.my-terraform.name node_group_name = "my-terraform" node_role_arn = aws_iam_role.my-terraform-node.arn subnet_ids = aws_subnet.my-terraform[*].id scaling_config { desired_size = 1 max_size = 1 min_size = 1 } depends_on = [ aws_iam_role_policy_attachment.my-terraform-node-AmazonEKSWorkerNodePolicy, aws_iam_role_policy_attachment.my-terraform-node-AmazonEKS_CNI_Policy, aws_iam_role_policy_attachment.my-terraform-node-AmazonEC2ContainerRegistryReadOnly, ] }
eks-worker-nodes 는 이름에서 알수 있듯 워커 노드에 대한 IAM role과 노드그룹, 오토스케일, 등의 값을 설정할 수 있습니다.
outputs.tf
locals { config_map_aws_auth = <<CONFIGMAPAWSAUTH apiVersion: v1 kind: ConfigMap metadata: name: aws-auth namespace: kube-system data: mapRoles: | - rolearn: ${aws_iam_role.my-terraform-node.arn} username: system:node:{{EC2PrivateDNSName}} groups: - system:bootstrappers - system:nodes CONFIGMAPAWSAUTH kubeconfig = <<KUBECONFIG apiVersion: v1 clusters: - cluster: server: ${aws_eks_cluster.my-terraform.endpoint} certificate-authority-data: ${aws_eks_cluster.my-terraform.certificate_authority[0].data} name: kubernetes contexts: - context: cluster: kubernetes user: aws name: aws current-context: aws kind: Config preferences: {} users: - name: aws user: exec: apiVersion: client.authentication.k8s.io/v1beta1 command: aws-iam-authenticator args: - "token" - "-i" - "${var.cluster-name}" KUBECONFIG } output "config_map_aws_auth" { value = local.config_map_aws_auth } output "kubeconfig" { value = local.kubeconfig }
outputs 는 해당 파일에 설정된 값을 통해 EKS 배포 완료 후 항목들을 확인하여 정상 배포되었음을 판단합니다.
providers.tf
terraform { required_version = ">= 0.12" } provider "aws" { region = var.aws_region access_key = "ACCESS_KEY" secret_key = "SECRET_KEY" } data "aws_availability_zones" "available" {} # Not required: currently used in conjunction with using # icanhazip.com to determine local workstation external IP # to open EC2 Security Group access to the Kubernetes cluster. # See workstation-external-ip.tf for additional information. provider "http" {}
providers 에서는 EKS 를 생성할 AWS Key 관련 값이 있습니다. 여기서 복사해두신 AWS IAM 액세스 키와 시크릿 액세스 키를 입력해주세요.
variables.tf
variable "aws_region" { default = "ap-northeast-2" } variable "cluster-name" { default = "my-terraform-eks" type = string }
variables 는 말그대로 여러가지 변수를 설정할 수 있습니다. 이번에는 EKS 를 생성할 리전과 클러스터 이름값을 설정했습니다.
vpc.tf
resource "aws_vpc" "my-terraform" { cidr_block = "10.0.0.0/16" tags = tomap({ "Name" = "my-terraform-eks-node", "kubernetes.io/cluster/${var.cluster-name}" = "shared", }) } resource "aws_subnet" "my-terraform" { count = 2 availability_zone = data.aws_availability_zones.available.names[count.index] cidr_block = "10.0.${count.index}.0/24" map_public_ip_on_launch = true vpc_id = aws_vpc.my-terraform.id tags = tomap({ "Name" = "my-terraform-eks-node", "kubernetes.io/cluster/${var.cluster-name}" = "shared", }) } resource "aws_internet_gateway" "my-terraform" { vpc_id = aws_vpc.my-terraform.id tags = { Name = "my-terraform-eks" } } resource "aws_route_table" "my-terraform" { vpc_id = aws_vpc.my-terraform.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.my-terraform.id } } resource "aws_route_table_association" "my-terraform" { count = 2 subnet_id = aws_subnet.my-terraform.*.id[count.index] route_table_id = aws_route_table.my-terraform.id }
vpc 에서는 생성하는 EKS 가 사용할 네트워크 환경을 구성합니다. VPC는 다른 리소스 등의 VPC와 동일해야 하기 때문에 새로 만드는 경우 모든 리소스를 새로 생성해 주어야 합니다.
EKS 배포 시작
파일이 이상없이 설정되었다면 아래와 같이 테라폼을 실행시켜 배포를 시작합니다.
PS> terraform init ... Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. PS> terraform apply ... Apply complete! Resources: 18 added, 0 changed, 0 destroyed.
배포 후 EKS 콘솔로 로그인 해보면 아래와 같이 생성된 클러스터를 확인할 수 있습니다.
쿠버네티스 명령어를 사용하기 위해서 EKS kubeconfig 파일을 받아주어야 합니다. 아래와 같이 설정합니다.
PS> aws eks update-kubeconfig --region ap-northeast-2 --name my-terraform-eks Added new context arn:aws:eks:ap-northeast-2:523296135686:cluster/my-terraform-eks to /Users/jeongjuho/.kube/config PS> kubectl get nodes NAME STATUS ROLES AGE VERSION ip-10-0-1-62.ap-northeast-2.compute.internal Ready <none> 4m29s v1.24.9-eks-49d8fe8
ec2 콘솔에서도 아래와 같이 ec2 인스턴스가 하나 생성된 것을 확인할 수 있습니다.
ACM 인증서 생성
EKS 에서 Ingress를 통해 서비스를 외부에 제공할 떄 TLS 암호화를 통해 https 를 구현해 줄 수 있습니다. 해당 arn 은 nginx controller 를 설정할때 사용하므로 메모장에 복사 해주세요. 아래와 같이 명령어를 입력합니다.
PS> aws acm request-certificate \ --domain-name www.my-terraform-web.com \ --validation-method DNS \ --idempotency-token 1234 \ --options CertificateTransparencyLoggingPreference=DISABLED ... { "CertificateArn": "arn:aws:acm:region:account:certificate/certificate_ID" }
NginxController 배포 및 설정
EKS 의 서비스에 외부 액세스를 제공하기위해 AWS는 자체 서비스인 로드밸런서를 이용하여 제공할 수 있습니다. EKS 에서 로드밸런서를 컨트롤 하는 컨테이너를 하나 생성해 주면 ingress 를 통해 외부 액세스를 제공할 수 있습니다.
alb 를 이용한 로드밸런서를 사용할 수 있지만 alb를 사용하면 서브도메인이 다른 여러 서비스 게이트웨이를 사용할 수 없으므로 nlb 를 이용한 컨트롤러를 사용하여 서브도메인에 따라 서비스를 분기처리 할 수 있도록 구성하겠습니다.
aws에서 기본 제공하는 nginx controller 템플릿을 다운로드 합니다.
PS> wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/aws/nlb-with-tls-termination/deploy.yaml
다운로드 받은 파일을 열어 아래 내용을 수정합니다.
service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "60" service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true" service.beta.kubernetes.io/aws-load-balancer-ssl-cert: 생성한_ACM_ARN service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https service.beta.kubernetes.io/aws-load-balancer-type: nlb
클러스터에 대한 VPC CIDR 을 변경해주세요. CIDR 은 클러스터의 VPC에서 확인할 수 있습니다.
proxy-real-ip-cidr: 172.31.0.0/16
수정된 매니페스트를 쿠버네티스에 적용합니다.
PS> kubectl apply -f deploy.yml namespace/ingress-nginx created serviceaccount/ingress-nginx created serviceaccount/ingress-nginx-admission created role.rbac.authorization.k8s.io/ingress-nginx created role.rbac.authorization.k8s.io/ingress-nginx-admission created clusterrole.rbac.authorization.k8s.io/ingress-nginx created clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created rolebinding.rbac.authorization.k8s.io/ingress-nginx created rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created configmap/ingress-nginx-controller created service/ingress-nginx-controller created service/ingress-nginx-controller-admission created deployment.apps/ingress-nginx-controller created job.batch/ingress-nginx-admission-create created job.batch/ingress-nginx-admission-patch created ingressclass.networking.k8s.io/nginx created validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
적용 후 배포된 리소스를 확인합니다.
PS> kubectl get all -n ingress-nginx --selector app.kubernetes.io/instance=ingress-nginx NAME READY STATUS RESTARTS AGE pod/ingress-nginx-admission-create-vcxgx 0/1 Completed 0 36s pod/ingress-nginx-admission-patch-xmswt 0/1 Completed 0 36s pod/ingress-nginx-controller-8594c5cbcb-ppt65 1/1 Running 0 36s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/ingress-nginx-controller LoadBalancer 172.20.28.176 a2c1e1dab574e405daea97ac0229e99b-da9c7f53e0502d31.elb.ap-northeast-2.amazonaws.com 80:30976/TCP,443:32413/TCP 36s service/ingress-nginx-controller-admission ClusterIP 172.20.85.253 <none> 443/TCP 36s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/ingress-nginx-controller 1/1 1 1 36s NAME DESIRED CURRENT READY AGE replicaset.apps/ingress-nginx-controller-8594c5cbcb 1 1 1 36s NAME COMPLETIONS DURATION AGE job.batch/ingress-nginx-admission-create 1/1 7s 36s job.batch/ingress-nginx-admission-patch 1/1 7s 36s
IngressClass가 잘 생성되었는지도 체크해줍니다.
PS> kubectl get ingressclass NAME CONTROLLER PARAMETERS AGE nginx k8s.io/ingress-nginx <none> 83s
WEB 서비스 생성 및 배포
python을 사용하여 간단한 웹 서비스를 만들어주겠습니다. web-server 디렉토리를 하나 생성하고 아래와 같이 코드를 작성해 주세요.
server.py
from flask import Flask app = Flask(__name__) @app.route('/hello') def hello(): return 'Hellow world!' if __name__ == '__main__': app.run(host="0.0.0.0")
노드에 올라갈 컨테이너 이미지를 만들어줍니다.
FROM python:3.8 RUN pip install flask RUN pip install httplib2 RUN pip install requests WORKDIR /app ADD server.py . CMD ["python3", "-u", "server.py"]
이미지를 빌드하여 레지스트리로 배포해 줍니다.
PS> docker build -t reg.my-harbor-repo.com/my-terraform/web:1.0 . PS> docker push reg.my-harbor-repo.com/my-terraform/web:1.0
EKS 배포가 될때 만약 Private 저장소를 이용하고 있고, 인증이 필요한 레지스트리라면 아래와 같이 Secret을 추가하여 저장소 인증을 대신할 수 있습니다.
PS> kubectl create secret docker-registry harbor --docker-server=reg.my-harbor-repo.com --docker-username=로그인아이디 --docker-password=비밀번호 --docker-email=이메일
EKS에 배포될 deployment와 서비스를 생성합니다.
deployment.yml
apiVersion: apps/v1 kind: Deployment metadata: name: webserver labels: app: web spec: replicas: 1 selector: matchLabels: app: web template: metadata: labels: app: web spec: imagePullSecrets: - name: harbor containers: - name: webserver image: reg.my-harbor-repo.com/my-terraform/web:1.0 imagePullPolicy: Always ports: - containerPort: 5000 name: flask --- apiVersion: v1 kind: Service metadata: name: webserver-service spec: type: NodePort selector: app: web ports: - port: 5000 targetPort: 5000
deployment와 service를 배포하고 팟이 잘 동작중인지 확인합니다.
PS> kubectl apply -f deployment.yml deployment.apps/webserver created service/webserver-service created PS> kubectl get pods NAME READY STATUS RESTARTS AGE webserver-6d869fc594-s94wb 1/1 Running 0 100s
NginxIngress 설정
EKS 의 서비스에 대한 Ingress를 설정하여 외부 연결을 열어줍니다.
ingress.yml
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-backend annotations: kubernetes.io/ingress.class: "nginx" spec: rules: - host: www.my-terraform-web.com http: paths: - backend: service: name: webserver-service port: number: 5000 path: / pathType: Prefix - host: my-terraform-web.com http: paths: - backend: service: name: webserver-service port: number: 5000 path: / pathType: Prefix
Ingress 가 잘 배포되었는지 확인해주세요.
PS> kubectl apply -f ingress.yml ingress.networking.k8s.io/ingress-backend created PS> kubectl get ing NAME CLASS HOSTS ADDRESS PORTS AGE ingress-backend <none> www.my-terraform-web.com,my-terraform-web.com a2c1e1dab574e405daea97ac0229e99b-da9c7f53e0502d31.elb.ap-northeast-2.amazonaws.com 80 108s
이제 브라우저를 열고 주소창에 https://www.my-terraform-web.com/hello 주소로 접근하면 방금 만든 플라스크 웹서버 화면을 볼 수 있습니다. (작성중에는 ACM DNS 인증을 진행하지 않아 https 오류가 발생했습니다.)
여기까지 1부를 정리했습니다. 자세한 설명없이 순간 순간 지나간것 같은데 글을 쓰는것도 보통일이 아니네요. 만약 부족한 부분이 있다면 댓글로 알려주세요. 제가 아는 선에서 답변 드리도록 하겠습니다.
2부에서 EFS와 EKS 를 사용해서 스토리지를 프로비저닝 하는 방법부터, 클러스터 운영까지 다뤄보도록 하겠습니다. 또한 지금은 클러스터 설정 파일을 모두 수동으로 작성하고 있지만 helm 이라는 설정 저장소에서 모든 설정을 관리할 수도 있습니다. 다음편에서 해당 부분까지 다뤄보도록 하겠습니다.
'Tech > DevOps' 카테고리의 다른 글
웹서버 오류 - bind() to[::]:443 failed 에러와 대처방법 (0) 2023.05.24 댓글