티스토리 뷰

728x90
반응형

개요

이번 포스팅에서는 AWS EKS 환경에 배포되어 있는 어플리케이션과 연동하기 위해 MariaDB를 Kubernetes에 배포해 보자.

  • ① AWS EKS 환경에 MariaDB 구축
  • ② AWS Code Series 파이프라인 구축

AWS EKS 환경에 MariaDB 구축

  • Amazon EFS CIS 드라이버 배포 및 테스트
  • Amazon EFS CIS 드라이버 설치
  • Amazon EFS CIS 드라이버 배포
  • Amazon EFS CSI 드라이버 테스트
  • AWS EKS에 MariaDB 설치
  • MariaDB 테스트

Amazon EFS CSI 드라이버 배포 및 테스트

CSI 드라이버의 서비스 계정이 AWS API를 호출할 수 있도록 허용하는 IAM ROLE을 생성한다.

1. GitHub에서 IAM 정책 문서 다운로드

curl -o iam-policy-example.json https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/v1.2.0/docs/iam-policy-example.json

2. IAM 정책 생성 (create-policy)

aws iam create-policy \
    --policy-name AmazonEKS_EFS_CSI_Driver_Policy \
    --policy-document file://iam-policy-example.json
  • 생성한 IAM 정책 중 Issuer 확인
aws eks describe-cluster --name your_cluster_name --query "cluster.identity.oidc.issuer" --output text
  • your_cluster_name - 클러스터 이름으로 변경
[root@ip-192-168-109-4 ~]# aws eks describe-cluster --name NRSON-EKS-CLUSTER --query "cluster.identity.oidc.issuer" --output text
https://oidc.eks.ap-northeast-2.amazonaws.com/id/abcdefghijklmnopqrstuvwxyz
[root@ip-192-168-109-4 ~]#

3. IAM Trust Policy 생성

  • IAM 신뢰 정책을 생성한 다음 Kubernetes 서비스 계정에 AssumeRoleWithWebIdentity 작업 부여
cat <<EOF > trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::AWS_ACCOUNT_ID:oidc-provider/oidc.eks.AWS_DEFAULT_REGION.amazonaws.com/id/<XXXXXXXXXXAAAAAXXXXXXXXXX>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.AWS_DEFAULT_REGION.amazonaws.com/id/<XXXXXXXXXXAAAAAXXXXXXXXXX>:sub": "system:serviceaccount:kube-system:efs-csi-controller-sa"
        }
      }
    }
  ]
}
EOF
  • AWS_ACCOUNT_ID - account id로 변경
  • AWS_DEFAULT_REGION - 해당 AWS 리전으로 변경
  • XXXXXXXXXXAAAAAXXXXXXXXXX - 2단계에서 반환된 값으로 변경

4. IAM 역할 생성

aws iam create-role \
  --role-name AmazonEKS_EFS_CSI_DriverRole \
  --assume-role-policy-document file://"trust-policy.json"

5. 새 IAM 정책을 역할에 연결

aws iam attach-role-policy \
  --policy-arn arn:aws:iam::<AWS_ACCOUNT_ID>:policy/AmazonEKS_EFS_CSI_Driver_Policy \
  --role-name AmazonEKS_EFS_CSI_DriverRole
  • AWS_ACCOUNT_ID - account id로 변경

Amazon EFS CIS 드라이버 설치

1. CIS 드라이버 설치

매니페스트를 다운로드하여 퍼블릭 Amazon ECR 레지스트리에 저장된 이미지를 사용해 드라이버를 설치한다.

$ kubectl kustomize "github.com/kubernetes-sigs/aws-efs-csi-driver/deploy/kubernetes/overlays/stable/?ref=release-1.3" > public-ecr-driver.yaml <br>

2. public-ecr-driver.yaml 편집 (ServiceAccount annotation 추가)

public-ecr-driver.yaml 파일을 편집하고 생성한 IAM 역할의 ARN으로 'efs-csi-controller-sa' Kubernetes ServiceAccount Object에 annotations을 추가한다.

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/name: aws-efs-csi-driver
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::<AWS_ACCOUNT_ID>:role/AmazonEKS_EFS_CSI_DriverRole
  name: efs-csi-controller-sa
  namespace: kube-system
  • AWS_ACCOUNT_ID - account id로 변경

Amazon EFS CSI 드라이버를 배포한다. Amazon EFS CSI 드라이버를 사용하면 ReadWriteMany 모드를 사용하여 여러 Pod를 동시에 볼륨에 쓸 수 있다.

3. public-ecr-driver.yaml 배포

$ kubectl apply -f public-ecr-driver.yaml
4. Amazon EKS 클러스터에 대한 VPC ID 확인
aws eks describe-cluster --name <CLUSTER_NAME> --query "cluster.resourcesVpcConfig.vpcId" --output text
  • CLUSTER_NAME - EKS CLUSTER로 변경
[root@ip-192-168-109-4 EFS]# aws eks describe-cluster --name your_cluster_name --query "cluster.resourcesVpcConfig.vpcId" --output text
vpc-vpcidddddddddd
[root@ip-192-168-109-4 EFS]#

5. VPC 클러스터에 대한 CIDR 범위 확인

aws ec2 describe-vpcs --vpc-ids YOUR_VPC_ID --query "Vpcs[].CidrBlock" --output text
  • YOUR_VPC_ID - 4번에서 확인한 vpc id로 변경
[root@ip-192-168-109-4 EFS]# aws ec2 describe-vpcs --vpc-ids vpc-vpcidddddddddd --query "Vpcs[].CidrBlock" --output text
192.168.0.0/16
[root@ip-192-168-109-4 EFS]#

6. Security Group 생성

> Amazon EFS 탑재 지점에 대한 인바운드 네트워크 파일 시스템(NFS) 트래픽을 허용하는 보안 그룹 생성

aws ec2 create-security-group --description efs-test-sg --group-name efs-sg --vpc-id YOUR_VPC_ID
  • YOUR_VPC_ID - 4번에서 확인한 vpc id로 변경

7. VPC의 리소스가 Amazon EFS 파일 시스템과 통신할 수 있도록 NFS 인바운드 규칙 추가

aws ec2 authorize-security-group-ingress --group-id sg-xxx --protocol tcp --port 2049 --cidr YOUR_VPC_CIDR
  • YOUR_VPC_CIDR - 5번에서 확인한 vpc cidr로 변경
  • sg-xxx - 6번에서 생성한 security group id로 변경

8. Amazon EKS 클러스터에 대한 Amazon EFS 파일 시스템 생성

aws efs create-file-system --creation-token eks-efs

참고: 나중에 사용하기 위해 FileSystemId를 저장합니다.

[root@ip-10-192-10-183 EFS]# aws efs create-file-system --creation-token eks-efs
{
    "SizeInBytes": {
        "ValueInIA": 0, 
        "ValueInStandard": 0, 
        "Value": 0
    }, 
    "FileSystemArn": "arn:aws:elasticfilesystem:ap-northeast-2:<AWS_ACCOUNT_ID>:file-system/fs-029c93b311a237e4e", 
    "ThroughputMode": "bursting", 
    "CreationToken": "eks-efs", 
    "Encrypted": false, 
    "Tags": [], 
    "CreationTime": 1662763838.0, 
    "PerformanceMode": "generalPurpose", 
    "FileSystemId": "fs-aaaabbbb00001111", 
    "NumberOfMountTargets": 0, 
    "LifeCycleState": "creating", 
    "OwnerId": "<AWS_ACCOUNT_ID>"
}
[root@ip-10-192-10-183 EFS]#

9. Amazon EFS mount target 생성

aws efs create-mount-target --file-system-id FileSystemId --subnet-id SubnetID --security-group sg-xxx
  • FileSystemId - 8번에서 생성한 file-system의 FileSystemId로 변경
  • SubnetID - Kubernetes WorkerNode에서 사용하는 Subnet으로 변경
  • sg-xxx - 6번에서 생성한 security group id로 변경.
  • 여러 서브넷에 mount target을 생성하려면 각 서브넷 ID에 대해 해당 과정을 반복 수행한다. mount targe이 있는 가용 영역의 모든 Amazon Elastic Compute Cloud(Amazon EC2) 인스턴스가 파일 시스템을 사용할 수 있다.

Amazon EFS CSI 드라이버 테스트

지금부터는 동일한 로그 파일에 write하는 두 개의 Pod를 배포하여 Amazon EFS CSI 드라이버를 테스트해 보도록 하자.

1. AWS GitHub에서 aws-efs-csi-driver 리포지토리 복제

git clone https://github.com/kubernetes-sigs/aws-efs-csi-driver.git

2. 작업 디렉터리를 Amazon EFS CSI 드라이버 테스트 파일이 포함된 폴더로 변경한다.

cd aws-efs-csi-driver/examples/kubernetes/multiple_pods/

3. 이전에 생성한 Amazon EFS 파일 시스템 ID를 검색한다.

aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text

4. specs/pv.yaml 파일에서 spec.csi.volumeHandle 값을 이전 단계의 Amazon EFS FileSystemId로 변경한다.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: efs-pv
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: efs-sc
  csi:
    driver: efs.csi.aws.com
    volumeHandle: fs-aaaaaaaaaaaaaa

5. 테스트에 필요한 Kubernetes 리소스를 생성한다.

kubectl apply -f specs/

6. 기본 네임 스페이스에 영구 볼륨을 나열하고 default/efs-claim 클레임이 있는 영구 볼륨을 찾는다.

kubectl get pv -w

7. 생성한 pv를 확인한다.

kubectl describe pv efs-pv

8. 두 개의 Pod가 동일한 파일에 데이터를 쓰고 있는지 테스트한다.

kubectl exec -it app1 -- tail /data/out1.txt 
kubectl exec -it app2 -- tail /data/out1.txt

AWS EKS에 MariaDB 설치

다음으로 생성한 EFS CIS Driver를 활용하여 MariaDB를 설치해 보도록 하자.

1. Persistent Volume 생성

  • db-pv.yaml 생성
apiVersion: v1
kind: PersistentVolume
metadata:
  name: db-pv-volume
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: efs-sc
  csi:
    driver: efs.csi.aws.com
    volumeHandle: fs-0e018b9e35d8b8c83
  • db-pv.yaml 파일 반영
[root@ip-192-168-109-4 specs]# kubectl apply -f db-pv.yaml 
persistentvolume/db-pv-volume created
[root@ip-192-168-109-4 specs]#
  • kubectl describe pv db-pv-volume
Name:            db-pv-volume
Labels:          <none>
Annotations:     kubectl.kubernetes.io/last-applied-configuration:
                   {"apiVersion":"v1","kind":"PersistentVolume","metadata":{"annotations":{},"name":"db-pv-volume"},"spec":{"accessModes":["ReadWriteOnce"],"...
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    efs-sc
Status:          Available
Claim:           
Reclaim Policy:  Retain
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        10Gi
Node Affinity:   <none>
Message:         
Source:
    Type:              CSI (a Container Storage Interface (CSI) volume source)
    Driver:            efs.csi.aws.com
    VolumeHandle:      fs-0e018b9e35d8b8c83
    ReadOnly:          false
    VolumeAttributes:  <none>
Events:                <none>

2. Persistent Volume Claim 생성

  • db-claim.yaml 생성
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-pv-claim
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: efs-sc
  resources:
    requests:
      storage: 10Gi
  • db-claim.yaml 파일 반영
[root@ip-192-168-109-4 specs]# kubectl apply -f db-claim.yaml 
persistentvolumeclaim/db-pv-claim created
[root@ip-192-168-109-4 specs]#
  • kubectl describe pvc db-pv-claim
Name:          db-pv-claim
Namespace:     default
StorageClass:  efs-sc
Status:        Bound
Volume:        db-pv-volume
Labels:        <none>
Annotations:   kubectl.kubernetes.io/last-applied-configuration:
                 {"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"db-pv-claim","namespace":"default"},"spec":{"access...
               pv.kubernetes.io/bind-completed: yes
               pv.kubernetes.io/bound-by-controller: yes
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      10Gi
Access Modes:  RWO
VolumeMode:    Filesystem
Mounted By:    mariadb-66457b6967-7h788
Events:        <none>

3. DB 정보 k8s Secret 생성

  • DB PASSWORD base64 암호화
[root@ip-192-168-109-4 specs]# echo -n 'DB_PASSWORD' | base64
dbpasswordencryptiondata
[root@ip-192-168-109-4 specs]#

 

DB_PASSWORD를 base64로 암호화 하여 secret에 저장한다.
  • db-secret.yaml 생성
---
apiVersion: v1
kind: Secret
metadata:
  name: mariadb-secret
data:
  password: dbpasswordencryptiondata
  • db-secret.yaml 반영
[root@ip-192-168-109-4 specs]# kubectl apply -f db-secret.yaml
secret/mariadb-secret created
[root@ip-192-168-109-4 specs]#
  • secret 조회
[root@ip-192-168-109-4 specs]# kubectl get secret
NAME                  TYPE                                  DATA   AGE
default-token-v242p   kubernetes.io/service-account-token   3      10h
mariadb-secret        Opaque                                1      11m
[root@ip-192-168-109-4 specs]#
  • kubectl describe secret mariadb-secret
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-pv-claim
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: efs-sc
  resources:
    requests:
      storage: 10Gi
[root@ip-192-168-109-4 MARIADB]# kubectl describe secret mariadb-secret
Name:         mariadb-secret
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
password:  11 bytes

4. MariaDB k8s Service 생성

  • mariadb-svc.yaml 파일 생성
---
apiVersion: v1
kind: Service
metadata:
  name: mariadb
spec:
  ports:
  - nodePort: 30306
    port: 3306
    protocol: TCP
    targetPort: 3306
  selector:
    app: mariadb
  type: NodePort
  • mariadb-svc.yaml 반영
[root@ip-192-168-109-4 specs]# kubectl apply -f db-claim.yaml 
service/mariadb created
[root@ip-192-168-109-4 specs]#
  • kubectl describe service mariadb
Name:              mariadb
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=mariadb
Type:              ClusterIP
IP:                10.100.173.172
Port:              <unset>  3306/TCP
TargetPort:        3306/TCP
Endpoints:         192.168.158.23:3306
Session Affinity:  None
Events:            <none>

5. MariaDB k8s Deployment 생성

  • mariadb-deployment.yaml 파일 생성
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mariadb
spec:
  selector:
    matchLabels:
      app: mariadb
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mariadb
    spec:
      containers:
      - image: mariadb:10.4
        name: mariadb
        ports:
        - containerPort: 3306
          name: mariadb
        volumeMounts:
        - name: mariadb-persistent-storage
          mountPath: /var/lib/mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mariadb-secret
              key: password
      volumes:
      - name: mariadb-persistent-storage
        persistentVolumeClaim:
          claimName: db-pv-claim

- spec.template.spec.comtainers.env.valueFrom.secretKeyRef의 key: [VALUE]의 VALUE는 secret에 등록한 data.[VALUE] 값을 매핑한다.

  • mariadb-svc.yaml 반영
[root@ip-192-168-109-4 MARIADB]# kubectl apply -f mariadb-deployment.yaml
deployment.apps/mariadb created
[root@ip-192-168-109-4 MARIADB]#
  • kubectl describe deployment mariadb
Name:               mariadb
Namespace:          default
CreationTimestamp:  Fri, 02 Sep 2022 15:16:11 +0000
Labels:             <none>
Annotations:        deployment.kubernetes.io/revision: 1
                    kubectl.kubernetes.io/last-applied-configuration:
                      {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"mariadb","namespace":"default"},"spec":{"selector":{"matc...
Selector:           app=mariadb
Replicas:           1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType:       Recreate
MinReadySeconds:    0
Pod Template:
  Labels:  app=mariadb
  Containers:
   mariadb:
    Image:      mariadb:10.7
    Port:       3306/TCP
    Host Port:  0/TCP
    Environment:
      MYSQL_ROOT_PASSWORD:  <set to the key 'password' in secret 'mariadb-secret'>  Optional: false
    Mounts:
      /var/lib/mysql from mariadb-persistent-storage (rw)
  Volumes:
   mariadb-persistent-storage:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  db-pv-claim
    ReadOnly:   false
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   mariadb-66457b6967 (1/1 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  56m   deployment-controller  Scaled up replica set mariadb-66457b6967 to 1
  • kubectl get pods -l app=mariadb
[root@ip-192-168-109-4 MARIADB]# kubectl get pods -l app=mariadb
NAME                       READY   STATUS    RESTARTS   AGE
mariadb-66457b6967-7h788   1/1     Running   0          57m
[root@ip-192-168-109-4 MARIADB]#

MariaDB 테스트

1. MariaDB Pod 접속

[root@ip-192-168-109-4 MARIADB]# kubectl exec -it mariadb-66457b6967-7h788 -- bash
root@mariadb-66457b6967-7h788:/#

2. mysql 접속

  • pod에 접속하여 직접 mysql client에 접속
root@mariadb-66457b6967-7h788:/# mysql -u root -p                   
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 10
Server version: 10.7.5-MariaDB-1:10.7.5+maria~ubu2004 mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.020 sec)

MariaDB [(none)]>

Enter password는 secret에 등록한 'DB_PASSWORD'이다.

  • remote에서 mariadb-client로 접속 (--mysql -h [POD_IP])
[root@ip-192-168-109-4 specs]# kubectl run -it --rm --image=mariadb:10.7 --restart=Never mariadb-client -- mysql -h 192.168.158.23 -p'DB_PASSWORD'
If you don't see a command prompt, try pressing enter.
MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.031 sec)

MariaDB [(none)]>

결론

지금까지 EFS를 활용하여 EKS 환경에 MariaDB를 구축해 보았다. 기업에서는 AWS 환경에서 RDB를 구축, 운영할 때 RDS를 사용할 것인지, 아니면 EC2 인스턴스에 사용 중인 데이터베이스를 직접 설치하는 방식을 선택할 것인지 결정해야 한다.

이를 선택하는 기준은 몇 가지가 있겠지만, 대표적으로 비용, 종속성 (벤더 Lock-In), 유지보수, 업그레이드, 보안 등이 선택의 기준이 된다.

Amazon RDS는 유지보수 효율성, 업그레이드 편의성 측면에서 강력한 기능을 지원하며, 특히 온프레미스 RDS를 AWS 환경 내 마이그레이션할 수 있는 도구도 다양하게 지원한다. (MySQL, Oracle, SQL Server, PostgreSQL, MariaDB, Aurora(MySQL과 호환) 등 지원)

EC2에 RDB를 직접 설치하는 경우 비용 측면에서 확연히 강점이 있다. 또한, 타 클라우드 환경으로 이전할 경우 손쉽게 이전이 가능하다. 다만, 기 구축되어 있는 RDB가 존재하고 이를 운영하는 조직이 있으며, 데이터 사이즈가 크지 않을 경우에는 EC2 인스턴스에 구축하는 것이 비용을 절감할 수 있겠지만, 신규 구축되는 조직의 경우 운영 유지보수를 직접 수행 할 경우 반대의 효과가 날수도 있다.

또한, 보안 측면에서 강력한 RDS이지만, 때로는 기업에서 관리하는 소프트웨어를 설치하여 운영해야 하는 경우도 발생할 수 있다. 특히 이는 보안 뿐만 아니라 메트릭을 측정하는 솔루션이거나, 알람을 알려주는 솔루션 등 시스템 통합 측면의 솔루션들의 유연한 관리가 필요할 수 있다. 이 경우 RDS보다는 EC2 기반의 관리가 효율적일 수 있다.

인프라 운영을 위한 어플리케이션, 제대로 설계된 자동화 그리고 데이터베이스 전문 관리 팀이 있는 기업의 경우 RDS가 꼭 필요하지 않을 수 있다. 따라서 반드시 라기보다는 RDS의 기능, 장단점 그리고 비용 요소를 정확히 파악하여 구축 방법에 대해 결정하는 것이 바람직할 것이다.


# 참조 (StatefulSet vs Deployment)

- StatefulSet 적용이 용이한 환경

  • IP 변경없이 직접 접근해야 하는 서비스 (Database, Redis, Kafka 등)
  • 데이터 유실이 발생하지 않아야 하는 서비스 (데이터 영속성을 유지해야 하는 경우)
  • 빈번한 업데이트와 다운타임이 발생되지 않아야 하는 중요한 서비스 (Rolling Update)

- StatefulSet의 특징

  • 스토리지 영속성을 유지하기 위해 Pod의 Storage는 PersistentVolume 또는 StorageClass로 프로비저닝해야 함
  • Pod의 네트워크ID를 유지하기 위해 headless service 필요

# 참조 (Headless Service)

Headless service란 .spec.clusterIP가 None으로 구성된 서비스를 의미한다. 서비스의 로드밸런싱 기능을 수행하지 않는 서비스이다. StatefulSet의 경우 Pod 별 개별 네트워크가 구성되어 있고 이와 통신하기 위한 DNS 정보를 할당하기 위한 용도로 Headless Service가 사용된다.

Pod DNS는 POD_NAME.SERVICE_NAME.NAMESPACE_NAME.svc.DNS_NAME으로 구성되며, 일반적으로는 mariadbpoda.mariadb.default.svc.cluster.local로 DNS가 구성된다. 따라서 이와 같은 DNS 정보를 유지하기 위해 Service 생성이 필요하다.

728x90
반응형