데이터를 수집하고, 서빙하기 위해 가장 일반적으로 사용되는 MySQL 서버를 Kubernetes Cluster 상에 설치해 보도록 하겠습니다. Cloud 환경에서는 각 Vendor 가 제공하는 Storage Solution 을 손쉽게 사용할 수 있지만, On-Premise 환경이기 때문에 Persistent Volume(pv)을 어떻게 설정할 것인지에 대해 고민하게 됩니다. 단순히 Hostpath 기반의 저장공간을 할당하는 법, 데이터의 연속성을 보장하기 위해 스케줄링정보를 가지고 있는 Local Persistent Volume 을 사용하는 법, Dynamic Provisioning 을 위해 NFS 혹은 Ceph 기반의 Storage Tier 를 별도로 구축하는 방법 등 여러가지 방법이 있는데 이번 포스팅에서는 두 번째 방법인 Local Persistent Volume 을 이용하여 PV 를 구성해보도록 하겠습니다.

그리고 프로젝트 계획 상 MySQL 의 HA 가 필수는 아님과 동시에 현재 Kubernetes Cluster 를 구성한 Raspberry Pi 4B 가 3대 뿐이며 그마저도 worker 노드는 2대이므로 리소스 관리를 위해 StatefulSet 대신 Deployment 로 배포하도록 하겠습니다. 향후 Raspberry Pi 4B 를 Scale-out 하게 되면 StatefulSet 기반의 이중화 구성을 고려해보도록 하겠습니다.

  • 작업폴더 설정
$ sudo mkdir -p /workspace/kubernetes/mysql
$ sudo chown -R truelifer:truelifer /workspace/kubernetes
$ cd /workspace/kubernetes/mysql

먼저 mysql 배포를 위한 폴더를 생성 후 적절한 권한을 설정해줍니다.

  • StorageClass 생성
$ vi storageClass-local-storage.yaml

vi 편집기로 StorageClass 생성을 위한 yaml 파일을 생성합니다.

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

Local Persistent Volume 은 Dynamic Provisioning 을 지원하지 않으므로 Static Provisioner 를 통해 직접 PV 를 생성하도록 하겠습니다. 또한 Persistent Volume Claim(pvc)의 Binding 시점을 Pod 가 생성된 후로 연기하도록 VolumeBindingMode 를 설정합니다. 위의 내용을 입력하고 파일을 저장합니다.

$ kubectl apply -f ./storageClass-local-storage.yaml

kubectl 명령을 통해 StorageClass 를 생성해 줍니다.

storageclass.storage.k8s.io/local-storage created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

  • Persistent Volume 생성
$ sudo mkdir -p /workspace/data/kubernetes/pv/mysql
$ sudo chmod 777 /workspace/data/kubernetes/pv/mysql

우선 worker1, worker2 각각 위의 명령을 통해 mysql 의 데이터가 저장될 폴더를 생성하고 적절한 권한을 부여합니다. 우선은 777으로 권한을 설정했으나 향후 RBAC 기반으로 전체적인 권한체계를 구성할 예정입니다.

$ vi pv-mysql.yaml

vi 편집기로 pv 생성을 위한 yaml 파일을 생성합니다.

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-mysql
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /workspace/data/kubernetes/pv/mysql
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- worker1

10GB 의 용량으로 설정하고 하나의 pod 만 접근하기 때문에 ReadWriteOnce, pod 가 삭제되더라도 데이터 보존을 위해 Retain 전략으로 설정합니다. NodeAffinity 설정을 통해 worker1 에 pv 를 생성하도록 합니다. 즉, MySQL pv 는 worker1 노드에 만들어지고 MySQL pod 역시 worker1 노드에 생성되게 됩니다.

$ kubectl apply -f ./pv-mysql.yaml

kubectl 명령을 통해 pv 를 생성해 줍니다.

persistentvolume/pv-mysql created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

  • Persistent Volume Claim 생성
$ vi pvc-mysql.yaml

vi 편집기로 pvc 생성을 위한 yaml 파일을 생성합니다.

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-mysql
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 10Gi

위에서 생성한 local-storage 타입으로 10GB 의 용량의 pvc 를 생성합니다.

$ kubectl apply -f ./pvc-mysql.yaml

kubectl 명령을 통해 pv 를 생성해 줍니다.

persistentvolumeclaim/pvc-mysql created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

$ kubectl get pvc

생성된 pvc 의 상태를 조회해봅니다.

NAME        STATUS   VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
pvc-mysql Bound pv-mysql 10Gi RWO local-storage 40s
$ kubectl get pv

생성된 pv 의 상태도 조회해봅니다.

NAME        STATUS   VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
pvc-mysql Bound pv-mysql 10Gi RWO local-storage 5m3s

VolumeBindingMode 를 WaitForFirstConsumer로 설정했기 때문에 Status 가 아직 Available 인 점을 확인할 수 있습니다.

  • Configmap, Secret 생성

기본적으로 MySQL 의 설정은 Remote Access 를 허용하지 않습니다. 또한 사용자 계정역시 root 외에는 생성되어 있지 않습니다. 이 설정들을 Deployment yaml 파일에 설정해도 되지만 배포 Context 라던지 상황에 따라 Variation 이 존재할 수 있으므로 유연한 설정을 위해 Configmap 으로 관리하도록 하겠습니다.

$ kubectl create configmap configmap-mysql --from-literal MYSQL_USER=truelifer --from-literal MYSQL_ROOT_HOST=%

사용자 계정을 생성하고 외부에서 접속가능한 설정을 Configmap 으로 생성합니다.

configmap/configmap-mysql created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

$ kubectl create secret generic secret-mysql --from-literal MYSQL_PASSWORD={비밀번호} --from-literal MYSQL_ROOT_PASSWORD={비밀번호}

위에서 생성한 사용자 계정에 매핑되는 비밀번호와 기본계정인 root 의 비밀번호를 Secret 을 통해 관리하겠습니다.

secret/secret-mysql created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

  • Deployment 생성
$ vi deployment-mysql.yaml

vi 편집기로 deployment 생성을 위한 yaml 파일을 생성합니다.

apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: mysql
spec:
containers:
- image: mysql/mysql-server:latest
name: mysql
env:
- name: MYSQL_USER
valueFrom:
configMapKeyRef:
name: configmap-mysql
key: MYSQL_USER
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: secret-mysql
key: MYSQL_PASSWORD
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: secret-mysql
key: MYSQL_ROOT_PASSWORD
- name: MYSQL_ROOT_HOST
valueFrom:
configMapKeyRef:
name: configmap-mysql
key: MYSQL_ROOT_HOST
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: volume-mysql
mountPath: /var/lib/mysql
volumes:
- name: volume-mysql
persistentVolumeClaim:
claimName: pvc-mysql

arm 기반의 Raspberry Pi 4B 에 몇가지 MySQL Docker Image 를 테스트해봤으나 모두 실패하고 mysql/mysql-server:latest 만 정상적으로 작동함을 확인했습니다. 위에서 생성한 pv, pvc, configmap, secret 을 기반으로 deployment yaml 파일을 작성합니다.

$ kubectl apply -f ./deployment-mysql.yaml

kubectl 명령을 통해 deployment 를 생성해 줍니다.

deployment.apps/mysql created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

$ kubectl get pods

생성된 pod 를 조회해봅니다.

NAME                     READY   STATUS    RESTARTS   AGE
mysql-78b9dbcf7b-hzsjz 1/1 Running 0 108s

Status 가 Running 상태로 정상적으로 배포되었음을 확인할 수 있습니다. 참고로 물리적인 H/W 의 성능에 따라서 배포가 되었음에도 불구하고 바로 로그인 되지 않을 수 있습니다. Pod 가 배포되더라도 기본적인 Initialization 이 완료되기까지는 조금의 시간이 필요하기 때문입니다. 제가 구성한 Kubernetes Cluster 의 H/W 기반이 Raspberry Pi 4B 이기에, 대략 1분정도의 Delay 가 발생함을 확인하였습니다.

  • Service 생성
$ vi service-mysql.yaml

vi 편집기로 service 생성을 위한 yaml 파일을 생성합니다.

apiVersion: v1
kind: Service
metadata:
name: service-mysql
spec:
type: NodePort
selector:
app: mysql
ports:
- protocol: TCP
port: 3306
nodePort: 30000
targetPort: 3306

보안 등의 이유로 MySQL 을 Kubernetes Cluster 내부에서만 접근 가능하도록 하기 위해서는 Service 의 Type 을 ClusterIP 로 설정하면 됩니다. 하지만 저는 Database 의 상태 및 데이터 조회를 위해 Kubernetes Cluster 외부에서 Client 툴을 통해 접속할 예정입니다. 이를 위해 NodePort 타입으로 설정하도록 합니다. Node 의 Port 기본 범위는 30000 ~ 32767로 정해져있습니다. 저는 30000 포트를 사용하도록 하겠습니다.

$ kubectl apply -f ./service-mysql.yaml

kubectl 명령을 통해 service 를 생성해 줍니다.

service/service-mysql created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

$ kubectl get services

생성된 service를 조회해봅니다.

NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 22d
service-mysql NodePort 10.100.190.233 <none> 3306:30000/TCP 20s

NodePort 타입으로 service 가 정상적으로 생성되었음을 확인할 수 있습니다.

  • IPTIME Port Fowarding 설정

위 과정을 통해 Kubernetes Cluster 상에 MySQL 을 배포하는 과정은 완료가 되었습니다. 외부에서 접속하기 위해서는 추가적으로 한 가지 작업이 더 남아있습니다. 공유기의 Port Fowarding 설정입니다. Chrome 브라우저를 통해 IPTIME 관리자페이지(192.168.0.1) 로 접속한 후 로그인을 합니다.

mysql-rasp 라는 이름으로 port fowarding rule 을 추가합니다. 외부에서 {도메인이름}:3306 으로 오는 접속을 내부의 Node(worker1 의 30000번 포트)로 연결해 주는 작업입니다.

  • 외부 접속 테스트

이제 모든 설정이 완료되었습니다. MySQL workbench 툴을 이용해 정상적으로 접속이 되는지 확인해보도록 하겠습니다.

우선 위와 같이 root 계정을 통해 connection 을 구성합니다. 우측 하단의 “OK” 를 클릭합니다.

Connection 구성이 완료되었습니다. 이제 접속해봅니다.

정상적으로 접속됨을 확인할 수 있습니다. 테스트로 버전을 확인하는 SELECT version(); 쿼리를 날려봅니다. 8.0.25 라는 쿼리 결과값이 출력됩니다.

그 후에 truelifer 계정에 모든 권한을 부여하는 GRANT ALL PRIVILEGES ON *.* TO ‘truelifer’@’%’; 쿼리를 실행합니다. 상황에 따라서 특정 DB 에 권한을 주거나, 세세하게 권한을 나누어 부여할 수 있습니다.

다시 Connection 을 구성하는 화면으로 돌아와 새로 만든 계정에 대해서도 Connection 을 구성해줍니다.

Connection 구성이 완료되었습니다. 접속해봅니다.

정상적으로 접속됨을 확인할 수 있습니다. 왼쪽 SCHEMAS 에 sys 데이터베이스가 보이는데 이것은 root 권한을 가진 사용자만 볼 수 있습니다. 따라서 정상적으로 root 권한이 부여되었음을 확인할 수 있습니다.

  • 마무리

Kubernetes Cluster 를 구성하고 최초의 배포를 해보았습니다. 다음 글에서는 Scrapy 프레임워크 기반의 Daum News Crawler 를 구현해보도록 하겠습니다.

--

--