2. 쿠버네티스 환경 구축과 애플리케이션 배포
2.1.2 예제 애플리케이션의 개요
장소 등록 서비스로 정보를 DB에 등록하는 배치 애플리케이션을 구성한다.
2.1.3 관련 서비스 및 용어
S3
설명
이 책에서는 FE 콘텐츠 배포 장소, 배치 애플리케이션에서 파일을 읽어오는 장소로 사용한다.
CloudFront
AWS가 제공하는 CDN으로 전 세계에 에지 로케이션을 가지고 있어 사용자가 접속한 가장 가까운 장소에서 콘텐츠를 제공할 수 있다.
RDS
설명
ECR
AWS가 제공하는 컨테이너 레지스트리 관리형 서비스로 DOCKER HUB와 비슷한 개념이다.
EC2
EC2의 컴퓨팅 서비스로
EC2 Instance Savings Plans
패밀리, 크기, AZ, 리전, OS, 테넌시와 상관없이 변경할 수 있는 절감모델
Compute Savings Plans
해당 리전의 패밀리 내에서 인스턴스간의 사용량을 변경할 수 있는 유연성을 제공, 최대 77%
등의 옵션으로 구매 가능하다.
ELB
AWS가 제공하는 로드밸런서로
ALB, NLB, GLB, CLB등이 제공됐지만 현재 CLB는 신규로 만들 수 없다.
ALB
L7 로드밸런서로 경로 기반 라우팅(URL), 호스트 기반 라우팅(HTTP Header) 등의 HTTP 고유 처리가 가능하며 CLB와 구분된다
NLB
L4 로드밸런서로 HTTP/HTTPS 프로토콜 외에도 사용 가능하다.
GWLB
L3 로드밸런서로 다른 회사의 가상 네트워킹 어플라이언스 배포, 확장, 실행을 돕고, 여러 회사의 어플라이언스에 대한 로드밸런싱 및 자동 조정 기능을 제공한다.
IAM
AWS 리소스에 접속하는 사용자나 권한을 관리하는 서비스
IAM에서 IAM 역할을 생성해 리소스에 설정함으로써 해당 리소스에 다른 리소스에 대한 접근 권한을 부여할 수있다.
2.1.4 EKS 구축에 사용하는 도구
EKS 클러스터 구축에는 두 가지 방법이 있다.
EKS 클러스터 구축 도구 eksctl을 이용하는 방법
AWS 콘솔 혹은 AWS CLI를 이용하는 방법들
eksctl
EKS 클러스터 구축 및 관리를 하기 위한 오픈소스 CLI 도구로
VPC, subnet, security group 등 EKS 클러스터를 구축하는데 필요한 리소스를 한 번에 구성할 수 있다.
VPC
CloudFormation
2.2 EKS 클러스터 구축
기본 리소스 생성
CloudFormation을 이용해 VPC 등의 기본 리소스를 생성한다.
base_resources_cfn
Copy AWSTemplateFormatVersion: '2010-09-09'
Parameters:
ClusterBaseName:
Type: String
Default: eks-work
TargetRegion:
Type: String
Default: ap-northeast-2
AvailabilityZone1:
Type: String
Default: ap-northeast-2a
AvailabilityZone2:
Type: String
Default: ap-northeast-2b
AvailabilityZone3:
Type: String
Default: ap-northeast-2c
VpcBlock:
Type: String
Default: 192.168.0.0/16
WorkerSubnet1Block:
Type: String
Default: 192.168.0.0/24
WorkerSubnet2Block:
Type: String
Default: 192.168.1.0/24
WorkerSubnet3Block:
Type: String
Default: 192.168.2.0/24
Resources:
EksWorkVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcBlock
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName} -VPC
WorkerSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone1
CidrBlock: !Ref WorkerSubnet1Block
VpcId: !Ref EksWorkVPC
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName} -WorkerSubnet1
WorkerSubnet2:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone2
CidrBlock: !Ref WorkerSubnet2Block
VpcId: !Ref EksWorkVPC
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName} -WorkerSubnet2
WorkerSubnet3:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone3
CidrBlock: !Ref WorkerSubnet3Block
VpcId: !Ref EksWorkVPC
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName} -WorkerSubnet3
InternetGateway:
Type: AWS::EC2::InternetGateway
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref EksWorkVPC
WorkerSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref EksWorkVPC
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName} -WorkerSubnetRouteTable
WorkerSubnetRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref WorkerSubnetRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
WorkerSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref WorkerSubnet1
RouteTableId: !Ref WorkerSubnetRouteTable
WorkerSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref WorkerSubnet2
RouteTableId: !Ref WorkerSubnetRouteTable
WorkerSubnet3RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref WorkerSubnet3
RouteTableId: !Ref WorkerSubnetRouteTable
Outputs:
VPC:
Value: !Ref EksWorkVPC
WorkerSubnets:
Value: !Join
- ","
- [!Ref WorkerSubnet1, !Ref WorkerSubnet2, !Ref WorkerSubnet3]
RouteTable:
Value: !Ref WorkerSubnetRouteTable
CloudFormation을 통해 기본 리소스를 생성한다.
다음과 같이 리소스가 구성된 것을 볼 수 있다.
2.2.2 EKS 클러스터 구축
eksctl 명령어로 EKS 클러스터를 구축한다.
Copy eksctl create cluster \
--vpc-public-subnets subnet-0e8042321293c7ae2,subnet-0009414b42f02c0c9,subnet-095d6a47ee9e2cdd5 \
--name eks-work-cluster \
--region ap-northeast-2 \
--version 1.19 \
--nodegroup-name eks-work-nodegroup \
--node-type t2.small
--nodes 2 \
--nodes-min 2 \
--nodes-max 5
해당 명령어 동작 중에 에러가 발생했다.
Copy 2024-06-04 20:49:09 [✖] AWS::IAM::Role/ServiceRole: CREATE_FAILED – "Resource handler returned message: \"Encountered a permissions error performing a tagging operation, please add required tag permissions. See https://repost.aws/knowledge-center/cloudformation-tagging-permission-error for how to resolve. Resource handler returned message: \"User: arn:aws:iam::566620743708:user/hojin is not authorized to perform: iam:CreateRole on resource: arn:aws:iam::566620743708:role/eksctl-eks-work-cluster-cluster-ServiceRole-o9PL6esmhBCb because no identity-based policy allows the iam:CreateRole action (Service: Iam, Status Code: 403, Request ID: 3218d612-0915-459c-a593-168f0254df9e)\"\" (RequestToken: 3073b698-9f51-d355-9de0-cdd413cc0b44, HandlerErrorCode: UnauthorizedTaggingOperation)"
2024-06-04 20:49:09 [!] 1 error(s) occurred and cluster hasn't been created properly, you may wish to check CloudFormation console
현재 로컬에서 root 계정이 아닌 hojin 계정을 생성해 사용중인데 해당 계정에 CreateRole이 없는 모양이었다.
따라서 aws configure 명령어를 통해 user를 Administrator로 전환하고 진행했다.
그 다음에 문제는 creating CloudFormation stack "eksctl-eks-work-cluster-cluster": operation error CloudFormation: CreateStack, https response error StatusCode: 400, RequestID: bdaf29da-cce6-43f1-a9b5-3cf8565efe00, AlreadyExistsException: Stack [eksctl-eks-work-cluster-cluster] already exists
따라서 해당 stack을 제거하고 진행했다.
kubeconfig 설정
eksctl은 EKS 클러스터 구축 중에 kubeconfig파일을 자동으로 업데이트하는데 kubeconfig파일은 쿠버네티스 클라이언트인 kubectl이 이용할 설정 파일로 접속 대상 쿠버네티스 클러스터의 접속 정보(컨트롤 플레인 URL, 인증 정보, 쿠버네티스 네임스페이스)를 저장하고 있다.
AWS CLI를 호출해 인증하기 위한 설정을 kubeconfig에 포함할 수 있다
kubectl: 쿠버네티스 클러스터에 명령을 내리는 CLI로 쿠버네티스는 오브젝트 관리를 위해 쿠버네티스 API를 사용하는데 kubectl CLI를 사용하면 해당 명령어가 쿠버네티스 API를 호출해 관련 동작을 수행한다.
Copy kubectl config get-contexts
출력 결과에서 컨텍스트가 활성화되어 있음을 알 수 있고, 노드 상태를 확인할 수 있다.
클러스터의 동작을 확인해보면
Copy kubectl apply -f 02_nginx_k8s.yaml
pod/nginx-pod created
Copy cat 02_nginx_k8s.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx-app
spec:
containers:
- name: nginx-container
image: nginx
ports:
- containerPort: 80
Copy kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-pod 1/1 Running 0 96s
정상적으로 pod가 생성되었음을 알 수 있다.
Copy kubectl port-forward nginx-pod 8080:80
Forwarding from 127.0.0.1:8080 - > 80
Forwarding from [::1]:8080 - > 80
2.3 데이터베이스 설정
예제 애플리케이션 DB 생성, DDL 적용 등 애플리케이션에서 사용하기 위한 설정을 수행한다.
Session Manager를 이용한 Bastion host 구축
bastion host 상에 깃 리포지터리 클론
RDS의 PostgreSQL을 사용하고, 다중화 없이 단일 가용 영역으로 구축한다.
기존 리소스 생성과 마찬가지로 CloudFormation에 미리 준비된 템플릿을 업로드해 생성한다.
하지만 기존의 템플릿이 에러로 실행되지 않아
Copy Resource handler returned message: "RDS does not support creating a DB instance with the following combination: DBInstanceClass=db.t2.micro, Engine=postgres, EngineVersion=11.22, LicenseModel=postgresql-license. For supported combinations of instance class and database engine version, see the documentation. (Service: Rds, Status Code: 400, Request ID: ac86e4f7-6daf-45c9-aea4-3fd0f53e7f6c)" (RequestToken: 77f0f6dc-85cc-f9de-1ede-6aa49cb7e339, HandlerErrorCode: InvalidRequest)
Instance class를 바꿔서 실행한다.
해당 링크 를 참고하자
10_rds_ope_cfn.yaml
Copy AWSTemplateFormatVersion: "2010-09-09"
Parameters:
EksWorkVPC:
Type: AWS::EC2::VPC::Id
WorkerSubnet1Block:
Type: String
Default: 192.168.0.0/24
WorkerSubnet2Block:
Type: String
Default: 192.168.1.0/24
WorkerSubnet3Block:
Type: String
Default: 192.168.2.0/24
OpeSubnetBlock:
Type: String
Default: 192.168.5.0/24
OpeServerRouteTable:
Type: String
ClusterBaseName:
Type: String
Default: eks-work
TargetRegion:
Type: String
Default: ap-northeast-2
AvailabilityZone1:
Type: String
Default: ap-northeast-2a
AvailabilityZone2:
Type: String
Default: ap-northeast-2b
AvailabilityZone3:
Type: String
Default: ap-northeast-2c
RdsSubnet1Block:
Type: String
Default: 192.168.3.0/24
RdsSubnet2Block:
Type: String
Default: 192.168.4.0/24
OpeServerInstanceType:
Type: String
Default: t3.micro
OpeServerImageId:
Type: String
Default: ami-004ca97dc8c3b41f8 # ap-northeast-2, GPU, Amazon Linux 2
# aws ssm get-parameter --name /aws/service/eks/optimized-ami/1.19/amazon-linux-2-gpu/recommended/image_id --region ap-northeast-2 --query "Parameter.Value" --output text
OpeServerVolumeSize:
Type: Number
Default: 8
Resources:
RdsSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone1
CidrBlock: !Ref RdsSubnet1Block
VpcId: !Ref EksWorkVPC
RdsSubnet2:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone2
CidrBlock: !Ref RdsSubnet2Block
VpcId: !Ref EksWorkVPC
RdsSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group for RDS
VpcId: !Ref EksWorkVPC
RdsIngressPostgreSQLWorker1:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref RdsSecurityGroup
CidrIp: !Ref WorkerSubnet1Block
IpProtocol: tcp
FromPort: 5432 # mysql 3306 mariadb 3396 postgresql 5432
ToPort: 5432
RdsIngressPostgreSQLWorker2:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref RdsSecurityGroup
CidrIp: !Ref WorkerSubnet2Block
IpProtocol: tcp
FromPort: 5432
ToPort: 5432
RdsIngressPostgreSQLWorker3:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref RdsSecurityGroup
CidrIp: !Ref WorkerSubnet3Block
IpProtocol: tcp
FromPort: 5432
ToPort: 5432
RdsIngressPostgreSQLOpe:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref RdsSecurityGroup
CidrIp: !Ref OpeSubnetBlock
IpProtocol: tcp
FromPort: 5432
ToPort: 5432
EksWorkDB:
Type: "AWS::RDS::DBInstance"
Properties:
DBInstanceIdentifier: eks-work-db
Engine: postgres # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html#aws-properties-rds-database-instance-properties
EngineVersion: 11 # https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_MySQL.html#MySQL.Concepts.VersionMgmt
DBInstanceClass: db.t3.micro
AllocatedStorage: 30
StorageType: gp2
DBName: eksworkdb
MasterUsername: !Join [ '' , [ '{{resolve:secretsmanager:' , !Ref RdsMasterSecret, ':SecretString:username}}' ]]
MasterUserPassword: !Join [ '' , [ '{{resolve:secretsmanager:' , !Ref RdsMasterSecret, ':SecretString:password}}' ]]
DBSubnetGroupName: !Ref EksWorkDBSubnetGroup
PubliclyAccessible: false
MultiAZ: false
PreferredBackupWindow: 18:00-18:30
PreferredMaintenanceWindow: sat:19:00-sat:19:30
AutoMinorVersionUpgrade: false
DBParameterGroupName: !Ref EksWorkDBParameterGroup
VPCSecurityGroups:
- !Ref RdsSecurityGroup
CopyTagsToSnapshot: true
BackupRetentionPeriod: 7
DeletionProtection: false
RdsMasterSecret:
Type: AWS::SecretsManager::Secret
Properties:
GenerateSecretString:
SecretStringTemplate: '{"username": "eksdbadmin"}'
GenerateStringKey: 'password'
PasswordLength: 16
ExcludeCharacters: '"@/\'''
Name: RdsMasterSecret
RdsUserSecret:
Type: AWS::SecretsManager::Secret
Properties:
GenerateSecretString:
SecretStringTemplate: '{"username": "mywork"}'
GenerateStringKey: 'password'
PasswordLength: 16
ExcludeCharacters: '"@/\''{}#%&*<>[]^`|'
Name: RdsUserSecret
RdsSecretAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref RdsMasterSecret
TargetId: !Ref EksWorkDB
TargetType: AWS::RDS::DBInstance
EksWorkDBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupName: subnet-group-eks-work-db
DBSubnetGroupDescription: "DB Subnet Group"
SubnetIds:
- !Ref RdsSubnet1
- !Ref RdsSubnet2
EksWorkDBParameterGroup:
Type: AWS::RDS::DBParameterGroup
Properties:
Family: postgres11 # aws rds describe-db-engine-versions --query "DBEngineVersions[].DBParameterGroupFamily"
Description: Parameter Group for PostgreSQL 11
OpeServerSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone1
CidrBlock: !Ref OpeSubnetBlock
VpcId: !Ref EksWorkVPC
OpeServerSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref OpeServerSubnet
RouteTableId: !Ref OpeServerRouteTable
OpeServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group for Ope
VpcId: !Ref EksWorkVPC
RdsIngressPostgreSQLFromOpe:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref RdsSecurityGroup
SourceSecurityGroupId: !Ref OpeServerSecurityGroup
IpProtocol: tcp
FromPort: 5432
ToPort: 5432
OpeServerRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${ClusterBaseName} -OpeServerRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM
Path: /
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
OpeServerInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref OpeServerRole
OpeServerEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
OpeServerEIPAssociation:
Type: AWS::EC2::EIPAssociation
Properties:
AllocationId: !GetAtt OpeServerEIP.AllocationId
InstanceId: !Ref OpeServerInstance
OpeServerInstance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref OpeServerInstanceType
ImageId: !Ref OpeServerImageId
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeType: gp2
VolumeSize: 20 # 원래는 8GB였음. 권고 사항이 20GB라고 함
DeleteOnTermination: true
NetworkInterfaces:
- SubnetId: !Ref OpeServerSubnet
AssociatePublicIpAddress: false
GroupSet:
- !Ref OpeServerSecurityGroup
DeviceIndex: 0
DeleteOnTermination: true
DisableApiTermination: false
IamInstanceProfile: !Ref OpeServerInstanceProfile
UserData:
Fn::Base64:
! Sub |
#!/bin/bash
set -o xtrace
yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm
/opt/aws/bin/cfn-signal --exit-code $? \
--stack ${AWS :: StackName} \
--resource NodeGroup \
--region ${AWS :: Region}
Outputs:
RDSEndpoint:
Value: !GetAtt EksWorkDB.Endpoint.Address
2.2.3 세션 관리자를 통한 베스천 호스트 접속
ec2 설정에서도 접속 가능하다.
해당 bastion host에 git, postgreSQL 클라이언트를 설치해준다.
Copy sudo yum install -y git
sudo amazon-linux-extras install -y postgresql11
git clone https://github.com/dybooksIT/k8s-aws-book.git
2.3.6 데이터베이스 엔드포인트 주소와 관리자 비밀번호 확인
DB 접속 전에 DB 접속정보가 필요하다. CloudFormation을 통해 다음 세 가지 정보를 확인한다.
데이터베이스 엔드포인트 주소 확인
엔드포인트 주소는 CloudFormation - Stacks - eks-work-rds - Outputs에서 확인 가능하다.
cloudformation 마지막 부분
Copy Outputs:
RDSEndpoint:
Value: !GetAtt EksWorkDB.Endpoint.Address
덕분인 것 같다.
eks-work-db.cvsiy1n9oexu.ap-northeast-2.rds.amazonaws.com
데이터베이스 관리자 비밀번호 확인
CloudFormation으로 구축한 DB는 AWS Secrets Manager가 RDS 관리자 비밀번호를 생성해 AWS Secrets Manager에서 확인 가능하다.
RdsMasterSecret: ]v[|mEh+<t~DTG7F
애플리케이션용 데이터베이스 사용자 비밀번호 확인
RdsUserSecret: J4(yv2fH~x,(LyaZ
2.3.7 데이터베이스 작업
DB에서 할 작업목록은 다음과 같다.
애플리케이션용 DB 사용자 생성
Copy createuser -d -U eksdbadmin -P -h eks-work-db.cvsiy1n9oexu.ap-northeast-2.rds.amazonaws.com mywork
DB 사용자를 생성한다.
애플리케이션용 DB 생성
Copy createdb -U mywork -h eks-work-db.cvsiy1n9oexu.ap-northeast-2.rds.amazonaws.com -E UTF8 myworkdb
DDL 실행 및 예제 데이터 불러오기
Copy \i k8s-aws-book/backend-app/scripts/10_ddl.sql
\i k8s-aws-book/backend-app/scripts/20_insert_sample_data.sql
두 명령어를 통해 postgreSQL 접속 상태에서 스크립트를 실행한다
2.4 API 애플리케이션 빌드와 배포
이 절에서는 API 애플리케이션을 빌드하고 컨테이너 이미지를 생성해 2.2절에서 생성한 EKS 클러스터에서 동작시킨다.
작업목록은 다음과 같다.
컨테이너 이미지를 저장할 컨테이너 레지스트리 준비
API 애플리케이션 컨테이너 이미지를 생성하고 컨테이너 레지스트리에 푸시
DB 접속용 비밀 정보를 보관하는 시크릿 생성
2.4.2 소스 코드 빌드와 컨테이너 이미지 생성
애플리케이션 빌드는 로컬 환경의 셸에서 진행한다.
Copy sudo chmod 755 ./gradlew
./gradlew clean build
이제 컨테이너 이미지를 생성한다.
Copy sudo docker build -t k8sbook/backend-app:1.0.0 --build-arg JAR_FILE=build/libs/backend-app-1.0.0.jar .
실행 과정에서 다음과 같은 에러가 떴다.
Copy ERROR: failed to solve: amazoncorretto:11: failed to resolve source metadata for docker.io/library/amazoncorretto:11: error getting credentials - err: exit status 1, out: ``
다음 링크 를 보고 credsStore -> credStore로 변경해 해결하긴 했지만 원인은 파악해봐야 하겠다.
2.4.3 컨테이너 레지스트리 준비
2.4.4 컨테이너 이미지 푸시
ECR 로그인
ECR 로그인을 위해 다음 명령어를 입력한다
Copy aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin 566620743708.dkr.ecr.ap-northeast-2.amazonaws.com
컨테이너 이미지 태그 설정과 푸시
컨테이너 이미지 푸시는 두 가지 순서로 진행한다.
docker tag 명령으로 컨테이너 이미지에 태그 설정
해당 태그에 대해 docker push 명령 실행
API 애플리케이션 컨테이너 이미지(k8s/backendapp:1.0.0)에 ECR 주소를 포함한 형태의 태그를 설정한다.
Copy docker tag k8sbook/backend-app:1.0.0 566620743708.dkr.ecr.ap-northeast-2.amazonaws.com/k8sbook/backend-app:1.0.0
해당 태그에 대해 docker push 명령어를 실행한다.
Copy docker push 566620743708.dkr.ecr.ap-northeast-2.amazonaws.com/k8sbook/backend-app:1.0.0
2.4.5 EKS 클러스터에 API 애플리케이션 배포
API 애플리케이션을 EKS 클러스터에 배포하기 위해 다음과 같은 단계를 진행한다.
네임스페이스 생성
쿠버네티스에서는 클러스터 하나를 네임스페이스라는 논리적 구획으로 구분해 관리할 수 있다.
여기서는 eks-work라는 네임스페이스를 생성하고 해당 네임스페이스에 API 애플리케이션을 배포한다.
네임스페이스는 쿠버네티스 오브젝트 설정 파일(매니페스트)라고 하며, kubectl apply 명령으로 적용해 생성한다.
쿠버네티스 오브젝트는 kubectl create으로도 생성 가능하지만(kubectl create namespace myns) 보통 매니페스트 파일을 이용해 생성하며, 파일 버전 관리와 같은 환경 구축에 사용한다.
Copy kubectl apply -f 20_create_namespace_k8s.yaml
apiVersion: v1
kind: Namespace
metadata:
name: eks-work
kubeconfig에 네임스페이스 반영
kubeconfig파일에는 컨텍스트마다 네임스페이스를 지정할 수 있다. 따라서 2.2.2에서 업데이트한 kubeconfig에 네임스페이스를 반영해야 한다
위에서 봤듯이 AWS CLI로 생성한 컨텍스트에는 네임스페이스가 지정되지 않았다.
여기서는 eks-work라는 새 네임스페이스를 이용하기 위해 eks-work가 설정된 새로운 컨텍스트를 생성하고 활성화한다.
Copy kubectl config set-context eks-work --cluster eks-work-cluster.ap-northeast-2.eksctl.io --user Administrator@eks-work-cluster.ap-northeast-2.eksctl.io --namespace eks-work
Context "eks-work" created.
kubectl config use-context eks-work
Switched to context "eks-work" .
Copy kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
Administrator@eks-work-cluster.ap-northeast-2.eksctl.io eks-work-cluster.ap-northeast-2.eksctl.io Administrator@eks-work-cluster.ap-northeast-2.eksctl.io
* eks-work eks-work-cluster.ap-northeast-2.eksctl.io Administrator@eks-work-cluster.ap-northeast-2.eksctl.io eks-work
minikube minikube minikube default
이제 eks-work가 지정된 컨텍스트가 생성대 활성화된 컨텍스트로 변경됐다는 것을 확인할 수 있다.
데이터베이스 접속용 시크릿 등록
API 애플리케이션이 DB에 접속하기 위한 비밀번호 등을 저장하는 시크릿을 생성한다 시크릿은 쿠버네티스 클러스터 안에 비밀번호 등의 비밀 정보를 보관하기 위한 도구다.
Copy DB_URL=jdbc:postgresql:eks-work-db.cvsiy1n9oexu.ap-northeast-2.rds.amazonaws.com/myworkdb DB_PASSWORD='J4(yv2fH~x,(LyaZ' envsubst < 21_db_config_k8s.yaml.template | kubectl apply -f -
secret/db-config created
21_db_config_k8s
Copy apiVersion: v1
kind: Secret
type : Opaque
metadata:
name: db-config
stringData:
db-url: ${DB_URL}
db-username: mywork
db-password: ${DB_PASSWORD}
API 애플리케이션 배포
API 애플리케이션을 배포한다.
Copy ECR_HOST=566620743708.dkr.ecr.ap-northeast-2.amazonaws.com envsubst < 22_deployment_backend-app_k8s.yaml.template | kubectl apply -f -
deployment.apps/backend-app created
22_deployment_backend
Copy apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-app
labels:
app: backend-app
spec:
replicas: 2
selector:
matchLabels:
app: backend-app
template:
metadata:
labels:
app: backend-app
spec:
containers:
- name: backend-app
image: ${ECR_HOST} /k8sbook/backend-app:1.0.0
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
- name: DB_URL
valueFrom:
secretKeyRef:
key: db-url
name: db-config
- name: DB_USERNAME
valueFrom:
secretKeyRef:
key: db-username
name: db-config
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
key: db-password
name: db-config
readinessProbe:
httpGet:
port: 8080
path: /health
initialDelaySeconds: 15
periodSeconds: 30
livenessProbe:
httpGet:
port: 8080
path: /health
initialDelaySeconds: 30
periodSeconds: 30
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
cpu: 250m
memory: 768Mi
lifecycle:
preStop:
exec :
command : [ "/bin/sh" , "-c" , "sleep 2" ]%
kubectl apply 명령으로 적용한 매니페스트 파일은 디플로이먼트라는 종류의 오브젝트를 생성하기 위한 것으로
디플로이먼트는 컨테이너를 배포, 디프로이먼트 뒤에 레플리카셋이 생성, 레플리카셋 뒤에는 파드가 생성된다. 아래에 결과가 있다.
Copy kubectl get all
NAME READY STATUS RESTARTS AGE
pod/backend-app-59b66ccd45-fmz8k 1/1 Running 0 9m59s
pod/backend-app-59b66ccd45-q8pxk 1/1 Running 0 9m59s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/backend-app 2/2 2 2 9m59s
NAME DESIRED CURRENT READY AGE
replicaset.apps/backend-app-59b66ccd45 2 2 2 9m59s
API 애플리케이션 외부 공개
API 애플리케이션 배포가 끝났지만 아직 외부에서는 호출이 불가능하다. 쿠버네티스는 배포된 파드를 외부에 공개하기 위해 서비스라는 리소스가 준비되어 있다. 여기서는 로드밸런서를 이용한다.
매니페스트 파일을 이용해 배포해보자
Copy kubectl apply -f 23_service_backend-app_k8s.yaml
service/backend-app-service created
23_service_backend-app_k8s
Copy apiVersion: v1
kind: Service
metadata:
name: backend-app-service
spec:
type : LoadBalancer
selector:
app: backend-app
ports:
- protocol: TCP
port: 8080
targetPort: 8080
EXTERNAL-IP에 주소가 공개된 것을 확인할 수 있다.
Copy kubectl get all
NAME READY STATUS RESTARTS AGE
pod/backend-app-59b66ccd45-fmz8k 1/1 Running 0 16m
pod/backend-app-59b66ccd45-q8pxk 1/1 Running 0 16m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/backend-app-service LoadBalancer 10.100.118.202 ac415c77bb14c4c45a1ad43a51570d97-482146193.ap-northeast-2.elb.amazonaws.com 8080:31069/TCP 3m2s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/backend-app 2/2 2 2 16m
NAME DESIRED CURRENT READY AGE
replicaset.apps/backend-app-59b66ccd45 2 2 2 16m
정상 작동 확인이 가능하다.
Copy curl -s http://ac415c77bb14c4c45a1ad43a51570d97-482146193.ap-northeast-2.elb.amazonaws.com:8080/health
{ "status" : "OK" }%
2.5 프런트엔드 애플리케이션 빌드와 배포
수행할 작업은 다음과 같다.
프런트엔드 콘텐츠를 전송할 S3 버킷과 CloudFront 배포 생성
CloudFront를 통해 접속해 애플리케이션 접속 확인
Nuxt.js로 구축된 SPA를 S3 + CloudFront 구성으로 구축하며, 빌드에는 Node.js를 사용한다
2.5.2 프런트엔드 애플리케이션 빌드
Copy npm install
BASE_URL = http://ac415c77bb14c4c45a1ad43a51570d97-482146193.ap-northeast-2.elb.amazonaws.com:8080 npm run build
패키지 다운로드, 통신하기 위한 ELB url을 매개변수로 입력해서 빌드하고, S3 버킷과 CloudFront 배포를 생성한다.
30_s3_cloudfront_cfn.yaml
Copy Parameters:
TargetRegion:
Type: String
Default: ap-northeast-2
BucketBaseName:
Type: String
Default: eks-work-frontend-
BucketSuffix:
Type: String
Resources:
ContentsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Join [ "" , [!Ref BucketBaseName, !Ref BucketSuffix]]
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: false
BlockPublicPolicy: false
IgnorePublicAcls: false
RestrictPublicBuckets: false
ContentsDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- Id: S3Origin
DomainName: !GetAtt ContentsBucket.DomainName
CustomOriginConfig:
OriginProtocolPolicy: match-viewer
Enabled: true
DefaultRootObject: index.html
Comment: !Ref AWS::StackName
DefaultCacheBehavior:
TargetOriginId: S3Origin
ForwardedValues:
QueryString: false
ViewerProtocolPolicy: allow-all
Outputs:
URL:
Value: !Join [ "" , [ "http://" , !GetAtt [ContentsDistribution, DomainName] ]]
DistributionID:
Value: !Ref ContentsDistribution
이제 콘텐츠를 업로드한다.
Copy aws s3 sync dist s3://eks-work-frontend-hj-bk-1 --delete --include "*" --acl public-read
upload: dist/.nojekyll to s3://eks-work-frontend-hj-bk-1/.nojekyll
upload: dist/index.html to s3://eks-work-frontend-hj-bk-1/index.html
upload: dist/_nuxt/870f68b.js to s3://eks-work-frontend-hj-bk-1/_nuxt/870f68b.js
upload: dist/_nuxt/41092ad.js to s3://eks-work-frontend-hj-bk-1/_nuxt/41092ad.js
upload: dist/200.html to s3://eks-work-frontend-hj-bk-1/200.html
upload: dist/_nuxt/LICENSES to s3://eks-work-frontend-hj-bk-1/_nuxt/LICENSES
upload: dist/favicon.ico to s3://eks-work-frontend-hj-bk-1/favicon.ico
upload: dist/_nuxt/d3f4395.js to s3://eks-work-frontend-hj-bk-1/_nuxt/d3f4395.js
upload: dist/v.png to s3://eks-work-frontend-hj-bk-1/v.png
upload: dist/_nuxt/b171587.js to s3://eks-work-frontend-hj-bk-1/_nuxt/b171587.js
upload: dist/_nuxt/0567ccc.js to s3://eks-work-frontend-hj-bk-1/_nuxt/0567ccc.js
upload: dist/regionDetail/index.html to s3://eks-work-frontend-hj-bk-1/regionDetail/index.html
upload: dist/_nuxt/9c2283f.js to s3://eks-work-frontend-hj-bk-1/_nuxt/9c2283f.js
왜인지 모르겠지만 cloudfront url로 접속했을때는 에러가 떴다.
2.6 배치 애플리케이션 빌드와 배포
수행할 작업은 다음과 같다.
배치 애플리케이션 파일 다운로드를 위한 S3 버킷 생성
배치 애플리케이션은 자바와 스프링 부트로 구축한 애플리케이션으로 동작시 환경 변수로 설정된 S3 버킷의 파일을 다운롣하고 파일 내용을 DB에 등록하고 쿠버네티스의 크론잡을 이용해 5분에 1번 동작시키도록 설정한다.
애플리케이션 빌드 및 컨테이너 이미지 생성
소스 코드 빌드
batch-app 디렉토리로 이동해 gradle build를 실행해주고 Dockerfile을 이용해 컨테이너 이미지를 생성한다.
Copy ./gradlew clean build
sudo docker build -t k8sbook/batch-app:1.0.0 --build-arg JAR_FILE=build/libs/batch-app-1.0.0.jar .
ECR 리포지터리 생성
ECR 로그인
Copy aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin 566620743708.dkr.ecr.ap-northeast-2.amazonaws.com
컨테이너 이미지 태그 생성과 푸시
태깅을 하고 레포지터리에 푸시한다.
Copy docker tag k8sbook/batch-app:1.0.0 566620743708.dkr.ecr.ap-northeast-2.amazonaws.com/k8sbook/batch-app:1.0.0
docker push 566620743708.dkr.ecr.ap-northeast-2.amazonaws.com/k8sbook/batch-app:1.0.0
40_s3_batch_cfn.yaml
Copy Resources:
BatchBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Join [ "" , [!Ref BucketBaseName, !Ref BucketSuffix]]
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: false
BlockPublicPolicy: false
IgnorePublicAcls: false
RestrictPublicBuckets: false
BatchUser:
Type: AWS::IAM::User
Properties:
Path: /
UserName: eks-work-batch-user
BatchUserAccessKey:
Type: AWS::IAM::AccessKey
Properties:
UserName: !Ref BatchUser
BatchBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref BatchBucket
PolicyDocument:
Statement:
- Action:
- s3:GetObject
- s3:DeleteObject
- s3:PutObject
Effect: Allow
Principal:
AWS: !GetAtt BatchUser.Arn
Resource: !Join [ "" , [!GetAtt BatchBucket.Arn, "/*" ]]
- Action:
- s3:ListBucket
Effect: Allow
Principal:
AWS: !GetAtt BatchUser.Arn
Resource: !GetAtt BatchBucket.Arn
BatchUserAccessKeyParameter:
Type: AWS::SSM::Parameter
Properties:
Name: BatchUserAccessKey
Type: String
Value: !Ref BatchUserAccessKey
BatchUserSecretAccessKeyParameter:
Type: AWS::SSM::Parameter
Properties:
Name: BatchUserSecretAccessKey
Type: String
Value: !GetAtt BatchUserAccessKey.SecretAccessKey
2.6.6 컨피그맵 생성
배치 애플리케이션에서 설정값을 저장하는 저장소로 컨피그맵을 사용하는데 쿠버네티스에서 설정값을 보관하기 위한 구조다. 배치 애플리케이션에서 사용할 S3 버킷 이름과 디렉토리 이름을 보관할 컨피그맵을 등록한다.
kubectl 명령어로도 설정할 수 있지만 버킷 이름의 접미사를 설정해야 하므로 로컬 작업 환경의 셸에서 envsubst를 사용한 명령을 사용한다
Copy BUCKET_SUFFIX = hojin envsubst < 41_config_map_batch_k8s.yaml.template | kubectl apply -f -
configmap/batch-app-config created
2.6.7 S3 접속용 액세스 키 확인과 시크릿 생성
S3 버킷은 허가된 특정 사용자(배치 어플리케이션)만 접속할 수 있도록 설정해야 하므로 S3 인증 정보를 배치 애플리케이션에 전달해야 한다.
API와 마찬가지로 비밀 정보는 쿠버네티스 시크릿을 사용한다.
액세스 키 확인
Copy envsubst < 42_batch_secrets_k8s.yaml.template | kubectl apply -f -
secret/batch-secret-config created
해당 명령어를 사용해 시크릿을 생성한다.
2.6.8 입력 파일 업로드
AWS CLI를 이용해 배치 어플리케이션이 사용할 입력 파일을 버킷에 업로드한다. CS는 batch-app/sample_dat/normal에 포함되어 있다.
Copy aws s3 sync ../batch-app/sample_data/normal s3://eks-work-batch-hojin/locationData --delete --include "*" --acl public-read
upload: ../batch-app/sample_data/normal/sample_location2.csv to s3://eks-work-batch-hojin/locationData/sample_location2.csv
upload: ../batch-app/sample_data/normal/sample_location1.csv to s3://eks-work-batch-hojin/locationData/sample_location1.csv
2.6.9 배치 애플리케이션 배포
배치 애플리케이션을 배포하기 전에 DB 내용과 S3 버킷을 확인한다.
데이터베이스 내용 확인
bastion host에 Systems Manager를 통해 접속한 후 데이터를 확인한다.
데이터베이스를 체크하고
S3 파일을 확인한다
배치 애플리케이션 배포
크론잡을 이용해 5의 배수의 시점에 동작하도록 설정한다.
Copy ECR_HOST = 566620743708.dkr.ecr.ap-northeast-2.amazonaws.com envsubst < 43_cronjob_k8s.yaml.template | kubectl apply -f -
cronjob.batch/batch-app created
Copy kubectl get all
NAME READY STATUS RESTARTS AGE
pod/backend-app-59b66ccd45-fmz8k 1/1 Running 0 7h9m
pod/backend-app-59b66ccd45-q8pxk 1/1 Running 0 7h9m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/backend-app-service LoadBalancer 10.100.118.202 ac415c77bb14c4c45a1ad43a51570d97-482146193.ap-northeast-2.elb.amazonaws.com 8080:31069/TCP 6h55m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/backend-app 2/2 2 2 7h9m
NAME DESIRED CURRENT READY AGE
replicaset.apps/backend-app-59b66ccd45 2 2 2 7h9m
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
cronjob.batch/batch-app */5 * * * * False 0 < non e > 101s
cron job이 실행중임을 알 수 있다.
Copy kubectl get all
NAME READY STATUS RESTARTS AGE
pod/backend-app-59b66ccd45-fmz8k 1/1 Running 0 7h10m
pod/backend-app-59b66ccd45-q8pxk 1/1 Running 0 7h10m
pod/batch-app-28626500-lmcdm 0/1 CrashLoopBackOff 1 (14s ago ) 30s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/backend-app-service LoadBalancer 10.100.118.202 ac415c77bb14c4c45a1ad43a51570d97-482146193.ap-northeast-2.elb.amazonaws.com 8080:31069/TCP 6h57m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/backend-app 2/2 2 2 7h10m
NAME DESIRED CURRENT READY AGE
replicaset.apps/backend-app-59b66ccd45 2 2 2 7h10m
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
cronjob.batch/batch-app */5 * * * * False 1 30s 3m7s
NAME COMPLETIONS DURATION AGE
job.batch/batch-app-28626500 0/1 30s 30s
5분 후 배치 어플리케이션이 실행됐음을 알 수 있다.
2.7 예제 애플리케이션 환경 삭제
리소스 삭제는 구축의 반대이며 쿠버네티스상의 리소스는 한 번에 삭제한다.
프런트엔드 애플리케이션용 S3 버킷과 CloudFront 배포 삭제