개요
Cloud 전문가 카페에서 EKS 스터디를 모집하길래 냅다 지원했다. 기존에 EKS를 사용해 본 경험이 있었지만, 이번 스터디를 통해서 세부적인 아키텍처 이해, CloudFormation을 처음 사용해보는 귀한 경험을 했고, 블로그에 내용을 정리하며 복습해보려 한다!
목표 : Amazon EKS 프로비젼 해보기
- Step 1. CloudFormation을 통해 Bastion Host 생성하기
- Step 2. Bastion Host에 여러가지 Tool 설치하기(Feat. CloudFormation)
- Step 3. eksctl 명령어를 사용하여 EKS Provision
- Step 4. EKS에 게임(Super Mario) 애플리케이션 배포하기
- Step 5. 워커노드 증설 해보기
이론
EKS 구성도
EKS(Elastic Kubernetes Service)는 Control-Plane과 Worker Node로 구성되어 있다. EKS가 기존(On-pre) Kubernetes와 크게 다른점은 Control-Plane을 CSP인 Amazon에서 관리해준다는 점이다.
현재 사용 가능한 Amazon EKS Kubernetes Version은 아래 사이트에서 확인 가능하다.
https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/kubernetes-versions.html
구현
준비사항
- aws 계정
- aws SSH Key
Step 1 . CloudFormation을 통해 Bastion Host 생성하기
AWS Console 로그인 후 CloudFormation 메뉴 접속 > 스택 생성 클릭
1단계 스택 생성 메뉴 > 템플릿 지정 > 템플릿 파일 업로드 선택
파일 선택 클릭 후 아래 첨부 파일 업로드 후 다음 클릭
2단계 스택 세부 정보 지정
- 스택 이름 : myeks-stack
- KeyName : bastions Host에 접속할 SSH Key
- 나머지 값은 기본값으로 진행!
- Public Subnet 2개, Private Subnet 2개가 자동 생성됨
- bastion EC2 1대가 Public Subnet에 1대 생성됨
- 이후 bastion에 로그인 하여 EKS를 설치한다.
3단계 스택 옵션 구성
별도 설정 없음 > 다음 클릭
4단계 스택 옵션 구성
별도 설정 없음 > 전송 클릭
아래 사진처럼 CREATE_IN_PROGRESS 상태에서 대기해보자!
Step 2. Bastion Host에 여러가지 Tool 설치하기(Feat. CloudFormation)
Step 1을 통해 스택을 생성하면 myeks-host라는 bastion이 생성된 것을 EC2 메뉴에서 확인할 수 있다.
스택을 생성할 때 사용한 yaml 파일을 확인해보면 UserData: 부분을 확인할 수 있는데 이 코드의 정의는 bastion EC2 생성단계에서 수행할 코드를 정의해놓는 것이다. 코드를 자세히 살펴보면 아래의 절차에 따라 여러가지 Tool 설치, 환경설정을 진행하는 것을 확인할 수 있다.
사실상 Step 1에서 모든 설정을 끝냈기 때문에 아래 코드를 대략이라도 이해하고 넘어가면 좋을 것 같다! (UserData: 부분)
- hostname 설정
- 시간 설정
- tree, jq, git, htop, lynx 등 패키지 설치
- kubectl, helm 명령어 설치
- eksctl, aws cli 설치 등
AWSTemplateFormatVersion: '2010-09-09'
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "<<<<< EKSCTL MY EC2 >>>>>"
Parameters:
- ClusterBaseName
- KeyName
- SgIngressSshCidr
- MyInstanceType
- LatestAmiId
- Label:
default: "<<<<< Region AZ >>>>>"
Parameters:
- TargetRegion
- AvailabilityZone1
- AvailabilityZone2
- Label:
default: "<<<<< VPC Subnet >>>>>"
Parameters:
- VpcBlock
- PublicSubnet1Block
- PublicSubnet2Block
- PrivateSubnet1Block
- PrivateSubnet2Block
Parameters:
ClusterBaseName:
Type: String
Default: myeks
AllowedPattern: "[a-zA-Z][-a-zA-Z0-9]*"
Description: must be a valid Allowed Pattern '[a-zA-Z][-a-zA-Z0-9]*'
ConstraintDescription: ClusterBaseName - must be a valid Allowed Pattern
KeyName:
Description: Name of an existing EC2 KeyPair to enable SSH access to the instances. Linked to AWS Parameter
Type: AWS::EC2::KeyPair::KeyName
ConstraintDescription: must be the name of an existing EC2 KeyPair.
SgIngressSshCidr:
Description: The IP address range that can be used to communicate to the EC2 instances
Type: String
MinLength: '9'
MaxLength: '18'
Default: 0.0.0.0/0
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
MyInstanceType:
Description: Enter t2.micro, t2.small, t2.medium, t3.micro, t3.small, t3.medium. Default is t2.micro.
Type: String
Default: t3.medium
AllowedValues:
- t2.micro
- t2.small
- t2.medium
- t3.micro
- t3.small
- t3.medium
LatestAmiId:
Description: (DO NOT CHANGE)
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
AllowedValues:
- /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
TargetRegion:
Type: String
Default: ap-northeast-2
AvailabilityZone1:
Type: String
Default: ap-northeast-2a
AvailabilityZone2:
Type: String
Default: ap-northeast-2c
VpcBlock:
Type: String
Default: 192.168.0.0/16
PublicSubnet1Block:
Type: String
Default: 192.168.1.0/24
PublicSubnet2Block:
Type: String
Default: 192.168.2.0/24
PrivateSubnet1Block:
Type: String
Default: 192.168.3.0/24
PrivateSubnet2Block:
Type: String
Default: 192.168.4.0/24
Resources:
# VPC
EksVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcBlock
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-VPC
# PublicSubnets
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone1
CidrBlock: !Ref PublicSubnet1Block
VpcId: !Ref EksVPC
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-PublicSubnet1
- Key: kubernetes.io/role/elb
Value: 1
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone2
CidrBlock: !Ref PublicSubnet2Block
VpcId: !Ref EksVPC
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-PublicSubnet2
- Key: kubernetes.io/role/elb
Value: 1
InternetGateway:
Type: AWS::EC2::InternetGateway
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref EksVPC
PublicSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref EksVPC
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-PublicSubnetRouteTable
PublicSubnetRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicSubnetRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicSubnetRouteTable
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet2
RouteTableId: !Ref PublicSubnetRouteTable
# PrivateSubnets
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone1
CidrBlock: !Ref PrivateSubnet1Block
VpcId: !Ref EksVPC
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-PrivateSubnet1
- Key: kubernetes.io/role/internal-elb
Value: 1
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone2
CidrBlock: !Ref PrivateSubnet2Block
VpcId: !Ref EksVPC
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-PrivateSubnet2
- Key: kubernetes.io/role/internal-elb
Value: 1
PrivateSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref EksVPC
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-PrivateSubnetRouteTable
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet1
RouteTableId: !Ref PrivateSubnetRouteTable
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet2
RouteTableId: !Ref PrivateSubnetRouteTable
# EKSCTL-Host
EKSEC2SG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: eksctl-host Security Group
VpcId: !Ref EksVPC
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-HOST-SG
SecurityGroupIngress:
- IpProtocol: '-1'
#FromPort: '22'
#ToPort: '22'
CidrIp: !Ref SgIngressSshCidr
EKSEC2:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref MyInstanceType
ImageId: !Ref LatestAmiId
KeyName: !Ref KeyName
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-host
NetworkInterfaces:
- DeviceIndex: 0
SubnetId: !Ref PublicSubnet1
GroupSet:
- !Ref EKSEC2SG
AssociatePublicIpAddress: true
PrivateIpAddress: 192.168.1.100
BlockDeviceMappings:
- DeviceName: /dev/xvda
Ebs:
VolumeType: gp3
VolumeSize: 20
DeleteOnTermination: true
UserData:
Fn::Base64:
!Sub |
#!/bin/bash
hostnamectl --static set-hostname "${ClusterBaseName}-host"
# Config convenience
echo 'alias vi=vim' >> /etc/profile
echo "sudo su -" >> /home/ec2-user/.bashrc
# Change Timezone
sed -i "s/UTC/Asia\/Seoul/g" /etc/sysconfig/clock
ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
# Install Packages
cd /root
yum -y install tree jq git htop lynx
# Install kubectl & helm
#curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.26.2/2023-03-17/bin/linux/amd64/kubectl
curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.25.7/2023-03-17/bin/linux/amd64/kubectl
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
# Install eksctl
curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
mv /tmp/eksctl /usr/local/bin
# Install aws cli v2
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip >/dev/null 2>&1
sudo ./aws/install
complete -C '/usr/local/bin/aws_completer' aws
echo 'export AWS_PAGER=""' >>/etc/profile
export AWS_DEFAULT_REGION=${AWS::Region}
echo "export AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION" >> /etc/profile
# Install YAML Highlighter
wget https://github.com/andreazorzetto/yh/releases/download/v0.4.0/yh-linux-amd64.zip
unzip yh-linux-amd64.zip
mv yh /usr/local/bin/
# Install krew
curl -LO https://github.com/kubernetes-sigs/krew/releases/download/v0.4.3/krew-linux_amd64.tar.gz
tar zxvf krew-linux_amd64.tar.gz
./krew-linux_amd64 install krew
export PATH="$PATH:/root/.krew/bin"
echo 'export PATH="$PATH:/root/.krew/bin"' >> /etc/profile
# Install kube-ps1
echo 'source <(kubectl completion bash)' >> /etc/profile
echo 'alias k=kubectl' >> /etc/profile
echo 'complete -F __start_kubectl k' >> /etc/profile
git clone https://github.com/jonmosco/kube-ps1.git /root/kube-ps1
cat <<"EOT" >> /root/.bash_profile
source /root/kube-ps1/kube-ps1.sh
KUBE_PS1_SYMBOL_ENABLE=false
function get_cluster_short() {
echo "$1" | cut -d . -f1
}
KUBE_PS1_CLUSTER_FUNCTION=get_cluster_short
KUBE_PS1_SUFFIX=') '
PS1='$(kube_ps1)'$PS1
EOT
# Install krew plugin
kubectl krew install ctx ns get-all # ktop df-pv mtail tree
# Install Docker
amazon-linux-extras install docker -y
systemctl start docker && systemctl enable docker
# CLUSTER_NAME
export CLUSTER_NAME=${ClusterBaseName}
echo "export CLUSTER_NAME=$CLUSTER_NAME" >> /etc/profile
# Create SSH Keypair
ssh-keygen -t rsa -N "" -f /root/.ssh/id_rsa
Outputs:
eksctlhost:
Value: !GetAtt EKSEC2.PublicIp
Step 3. eksctl 명령어를 사용하여 EKS Provision
bastion Host 접속하기
EC2 메뉴에 접속하면 myeks-host 인스턴스가 생성된 것을 확인할 수 있다. 인스턴스의 퍼블릭 IPv4 주소를 확인하고 SSH를 통해 접속해보자.
bastion Host aws 권한 부여하기
원격(bastion)에서 eksctl 명령어를 사용하여 EKS를 Provision하기 위해선 권한이 필요하다. 이를 위해 AWS 계정의 Access-Key와 Secret-Key가 필요하다. A-K, S-K는 AWS의 IAM메뉴에서 생성 가능하다. Admin 계정으로 발급하여 진행하였다.
터미널에서 aws configure 명령어를 통해 권한을 부여하자! (액세스, 시크릿 키는 해킹 시 금전적인 피해를 볼 수 있기에 외부에 노출되어선 안됨을 명심하자..!)
[root@myeks-host ~]# aws configure
AWS Access Key ID [None]: (Access-Key)
AWS Secret Access Key [None]: (Secret_Key)
Default region name [None]: ap-northeast-2
Default output format [None]:
(확인)잘 설정이 됐는지 aws s3 ls 명령어를 통해 오류 없이 실행되는지 살펴본다.
변수 설정하기
export VPCID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$CLUSTER_NAME-VPC" | jq -r .Vpcs[].VpcId)
echo "export VPCID=$VPCID" >> /etc/profile
export PubSubnet1=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-PublicSubnet1" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet2=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-PublicSubnet2" --query "Subnets[0].[SubnetId]" --output text)
echo "export PubSubnet1=$PubSubnet1" >> /etc/profile
echo "export PubSubnet2=$PubSubnet2" >> /etc/profile
변수 확인하기
[root@myeks-host ~]# echo $AWS_DEFAULT_REGION
ap-northeast-2
[root@myeks-host ~]# echo $CLUSTER_NAME
myeks
[root@myeks-host ~]# echo $VPCID
vpc-..........
[root@myeks-host ~]# echo $PubSubnet1,$PubSubnet2
subnet-.........,subnet-.........
클러스터 생성 여부 확인하기
아래 명령어를 통해 클러스터를 만들 수 있는지 확인한다. 실제 클러스터가 만들어지는 것은 아니라 테스트만 하는 명령어 이다! --dry-run 옵션
eksctl create cluster --name $CLUSTER_NAME --region=$AWS_DEFAULT_REGION --nodegroup-name=$CLUSTER_NAME-nodegroup --node-type=t3.medium --node-volume-size=30 --vpc-public-subnets "$PubSubnet1,$PubSubnet2" --version 1.24 --ssh-access --external-dns-access --dry-run | yh
클러스터 생성하기
--dry-run 에서 문제 없다면 아래 명령어를 통해 EKS 클러스터를 생성해보자! 시간은 약 15분 소요된다.
eksctl create cluster --name $CLUSTER_NAME --region=$AWS_DEFAULT_REGION --nodegroup-name=$CLUSTER_NAME-nodegroup --node-type=t3.medium --node-volume-size=30 --vpc-public-subnets "$PubSubnet1,$PubSubnet2" --version 1.24 --ssh-access --external-dns-access --verbose 4
- --name : 생성할 클러스터의 이름을 지정
- --region : 클러스터를 생성할 AWS 리전을 지정
- --nodegroup-name : 노드 그룹의 이름을 지정
- --node-type : 생성할 노드의 인스턴스 유형을 지정
- --node-volume-size : 노드의 볼륨 크기를 지정
- --vpc-public-subnets : 클러스터에서 사용할 VPC의 공용 서브넷을 지정
- --version : 생성할 EKS 클러스터의 버전을 지정
- --ssh-access : SSH 접속을 허용
- --external-dns-access : 외부 DNS 접근을 허용
- --verbose : 자세한 로그 출력을 활성화
클러스터 정보 확인하기
k get nodes 명령어를 입력하여 워커노드 정보를 확인해보자. 아래와 같이 출력 된다면 문제 없이 EKS가 생성된 것이다. 따라서 AWS의 EKS 메뉴에 접속하면 방금 생성한 클러스터 정보, EC2 메뉴에서 워커 노드들이 생성 된 것을 확인할 수 있다.
(admin@myeks:N/A) [root@myeks-host ~]# k get nodes
NAME STATUS ROLES AGE VERSION
ip-192-168-1-52.ap-northeast-2.compute.internal Ready <none> 12m v1.24.13-eks-0a21954
ip-192-168-2-196.ap-northeast-2.compute.internal Ready <none> 12m v1.24.13-eks-0a21954
Step 4. EKS에 게임(Super Mario) 애플리케이션 배포하기
배포 Yaml파일 다운로드 받기
아래명령어를 입력하여 yaml 파일을 다운로드 받아보자
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/1/mario.yaml
EKS에 배포하기
아래명령어를 입력하여 yaml 파일을 기반으로 쿠버네티스 리소스를 생성해보자.
kubectl apply -f mario.yaml
배포확인 및 접속해보기
아래명령어를 입력하여 배포된 리소스를 확인하고, 외부 접속 주소를 확인하자. service 타입을 확인하면 mario이름의 LoadBalancer가 배포된 것을 확인할 수 있다. 해당 LB의 EXTERNAL-IP를 보면 AWS의 CLB 주소가 명시된 것을 확인할 수 있으며 CLB, POD, LB 등 기본 설정이 완료되기 까지 기다린 후 EXTERNAL-IP를 WEB상에 기입해보면 정상적으로 접근할 수 있는것을 확인할 수 있다.
(admin@myeks:N/A) [root@myeks-host ~]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/mario-687bcfc9cc-snv4f 1/1 Running 0 28s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 33m
service/mario LoadBalancer 10.100.204.27 ....-1432148654.ap-northeast-2.elb.amazonaws.com 80:30644/TCP 28s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/mario 1/1 1 1 28s
NAME DESIRED CURRENT READY AGE
replicaset.apps/mario-687bcfc9cc 1 1 1 28s
Step 5. 삭제하기
CLI를 통해 삭제하기
# EKS 클러스터 삭제
eksctl delete cluster --name $CLUSTER_NAME
# AWS CloudFormation 스택 삭제
aws cloudformation delete-stack --stack-name myeks-stack
# 콘솔에서 로드밸런서는 별도로 삭제 해야함!! EC2-> 로드밸런서
Appendix
1주차. EKS-Amazon EKS 설치 - 2023 : https://brunch.co.kr/@topasvga/3207
Cloud전문가 카페 주소 : https://cafe.naver.com/dnspro
'클라우드' 카테고리의 다른 글
[Docker] Docker로 컨테이너 배포하기 (0) | 2023.07.14 |
---|---|
[Terraform] 테라폼 - AWS - 설치 (0) | 2023.07.03 |
kubeadm으로 고가용성 클러스터 생성-1 (0) | 2023.04.03 |
[k8s] Deployment (0) | 2023.03.08 |
[k8s]Replication Controller & Replicaset (0) | 2023.03.08 |