POD의 영구 저장소를 위해 DB 서버를 할당하는 것은 매우 비효율적인 일이다.
로그 저장, Kafka partition 보존 등을 위해 POD의 저장소를 영구 저장소에 옮겨야하는데 EKS환경에서 사용할 수 있는 볼륨으로 EBS와 EFS가 있다.
나는 EFS를 선택했는데 그 이유는 EBS는 EC2 노드에 종속성이 있으며 같은 AZ에서만 사용 가능하다는 단점이 있기 때문이다.
먼저 EKS가 EFS를 사용할 수 있도록 efs-csi-driver를 설치한다.
csi란 Container Storage Interface
의 약자로 Container 환경에서 스토리지를 편리하게 활용하기 위한 드라이버이다.
이를 통해 환경에 따라 ( K8S, Only Docker, Bare metal ) 드라이버를 따로 구성할 필요없이 하나의 추상화된 접근 인터페이스를 통해 접근 가능하다.
# helm 설치
$ curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 > get_helm.sh
$ chmod 700 get_helm.sh
$ ./get_helm.sh
# 아래 에러 발생시 vi get_helm.sh을 입력한 후
# 편집기에서 HELM_INSTALL_DIR을 $PATH와 일치하도록 변경
helm not found. Is /usr/local/bin on your $PATH?
Failed to install helm
For support, go to https://github.com/helm/helm.
# helm에 aws-efs-csi-driver repository 추가
$ helm repo add aws-efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver/
# repository 업데이트
$ helm repo update
# 드라이버 설치
# https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/add-ons-images.html
$ helm upgrade -i aws-efs-csi-driver aws-efs-csi-driver/aws-efs-csi-driver \
--namespace kube-system \
--set image.repository={위의 링크에서 리전별 값 확인}/eks/aws-efs-csi-driver \
--set controller.serviceAccount.create=false \
--set controller.serviceAccount.name=efs-csi-controller-sa
# 정책 파일 다운로드
curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/docs/iam-policy-example.json
# 정책 생성
aws iam create-policy --policy-name $ACCOUNT-AmazonEKS_EFS_CSI_Driver_Policy --policy-document file://iam-policy-example.json
# 정책을 사용해 역할과 서비스 어카운트 생성
eksctl create iamserviceaccount \
--cluster $ACCOUNT-cluster \
--namespace kube-system \
--name efs-csi-controller-sa \
--attach-policy-arn arn:aws:iam::$AWSNUM:policy/$ACCOUNT-AmazonEKS_EFS_CSI_Driver_Policy \ --approve \
--region $AWSREGION
# EFS CSI 드라이버 배포 YML 파일 다운로드
kubectl kustomize \
"github.com/kubernetes-sigs/aws-efs-csi-driver/deploy/kubernetes/overlays/stable/?ref=release-1.4" > public-ecr-driver.yaml
위 방식대로 역할을 생성하면 AmazonEFS 에 대한 Full Access가 아닌 하위 권한을 받게 된다. 필요하다면 노드 그룹의 역할에
Amazon Elastic File System Full Access
를 부여하자
파일에 있는 Service Account 생성 부분을 삭제한다. ( 이미 만들었다. )
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/name: aws-efs-csi-driver
name: efs-csi-controller-sa
namespace: kube-system
---
kubectl apply -f public-ecr-driver.yaml
# 1. EKS 클러스터의 vpc_id 값 조회
$ vpc_id=$(aws eks describe-cluster --name $ACCOUNT-cluster --query "cluster.resourcesVpcConfig.vpcId" --output text)
# 2. EKS 클러스터가 속한 vpc의 network cidr 조회
$ cidr_range=$(aws ec2 describe-vpcs --vpc-ids $vpc_id --query "Vpcs[].CidrBlock" --output text --region $AWSREGION)
# 3. EFS의 보안그룹 생성
$ security_group_id=$(aws ec2 create-security-group --group-name $ACCOUNT-EfsSG --description "$ACCOUNT-EfsSG" --vpc-id $vpc_id --output text)
# 4. 보안그룹에 인바운드 정책 추가
$ aws ec2 authorize-security-group-ingress --group-id $security_group_id --protocol tcp --port 2049 --cidr $cidr_range
# 5. EFS 생성
$ file_system_id=$(aws efs create-file-system --region $AWSREGION --performance-mode generalPurpose --tag Key=Name,Value=$ACCOUNT-efs --query 'FileSystemId' --output text)
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$vpc_id" --query 'Subnets[*].{SubnetId: SubnetId,AvailabilityZone: AvailabilityZone,CidrBlock: CidrBlock}' --output table
EFS 콘솔에 들어가 생성된 EFS FileSystem ID를 확인한다.
# 타겟 서브넷에 Mount Target을 생성하고 보안그룹 적용
aws efs create-mount-target --file-system-id $file_system_id --subnet-id <subnet-id> --security-groups $security_group_id
aws efs create-mount-target --file-system-id $file_system_id --subnet-id <subnet-id> --security-groups $security_group_id
EFS 콘솔에서 네트워크에 해당 VPC가 탑재됐는지 확인한다.
provider "aws" {
region = var.region # 원하는 AWS 리전으로 변경
access_key = var.access_key
secret_key = var.secret_key
}
# 1. EKS 클러스터의 vpc_id 값 조회
data "aws_eks_cluster" "eks_cluster" {
name = var.cluster_name
}
# 2. EKS 클러스터가 속한 vpc의 network cidr 조회
data "aws_vpc" "vpc" {
id = data.aws_eks_cluster.eks_cluster.vpc_config[0].vpc_id
}
# 3. EFS의 보안그룹 생성
resource "aws_security_group" "efs_security_group" {
name = "${var.tag_name}-mongo-EfsSG" # 보안 그룹 이름에 맞게 수정
description = "EFS Security Group"
vpc_id = data.aws_vpc.vpc.id
}
# 4. 보안그룹에 인바운드 정책 추가
resource "aws_security_group_rule" "efs_inbound_rule" {
type = "ingress"
from_port = 2049
to_port = 2049
protocol = "tcp"
security_group_id = aws_security_group.efs_security_group.id
cidr_blocks = [data.aws_vpc.vpc.cidr_block]
}
# 5. EFS 생성
resource "aws_efs_file_system" "mongoDB-EFS" {
performance_mode = "generalPurpose"
tags = {
Name = "suite-mongo-test-efs" # EFS 이름에 맞게 수정
}
}
# JSON 데이터 구문 분석
locals {
terraform_outputs = jsondecode(file("../../terraform_outputs.json"))
}
# 타겟 서브넷에 Mount Target을 생성하고 보안그룹 적용
resource "aws_efs_mount_target" "mongoDB" {
count = length(local.terraform_outputs.private_subnet_ids) - 1
file_system_id = aws_efs_file_system.mongoDB-EFS.id
subnet_id = local.terraform_outputs.private_subnet_ids.value[count.index]
security_groups = [aws_security_group.efs_security_group.id]
}
output "efs_dns_name" {
value = aws_efs_file_system.mongoDB-EFS.dns_name
}
json 파일의 경우 vpc를 만든 후 shell script를 통해 만들어 준다.
Terraform 참조 링크
#!/bin/bash
# Terraform output 명령을 사용하여 출력 값을 추출합니다.
output_values=$(terraform output -json)
# JSON 파일로 저장합니다. 원하는 파일 이름으로 변경하세요.
echo $output_values > terraform_outputs.json
echo "Terraform outputs have been saved to terraform_outputs.json"
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: efs-sc
parameters:
directoryPerms: "700"
fileSystemId: <fs-system-id>
provisioningMode: efs-ap
provisioner: efs.csi.aws.com
kubectl create -f ./efs-sc.yml
K8S 볼륨
위에서 살펴봤듯이 인프라 관리자가 PV를 만들어놓으면, 어플리케이션 개발자가 PVC를 POD에 붙여 볼륨을 얻어온다.
PV를 미리 만들어놓고, 나눠주는 방식이면 Static Mountain,
PVC를 만들어서 요청만하면 자동으로 볼륨이 붙는게 Dynamic Mountain이다.
PV하나에 PVC 하나만 붙기 때문에 관리해야 할 YML파일이 늘어나기 때문에
프로젝트의 규모가 크지 않고 체계적인 볼륨 관리의 긍정적 효과보다 관리의 비용이 더 크기 때문에 Dynamic Mountain을 선택했다.
cat <<EOF | kubectl create -f - apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: efs-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: efs-sc
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Pod
metadata:
name: efs-pod1
spec:
containers:
- name: app
image: redis
volumeMounts:
- name: efs-pv
mountPath: /mount1
volumes:
- name: efs-pv
persistentVolumeClaim:
claimName: efs-pvc
---
apiVersion: v1
kind: Pod
metadata:
name: efs-pod2
spec:
containers:
- name: app
image: redis
volumeMounts:
- name: efs-pv
mountPath: /mount2
volumes:
- name: efs-pv
persistentVolumeClaim:
claimName: efs-pvc
EOF
kubectl exec -it efs-pod1 -- /bin/bash
cd /mount1
echo hello efs > test.txt
exit
kubectl exec -it efs-pod2 -- /bin/bash
cd /mount2
echo test.txt
exit
kubectl delete pod,pvc --all