퍼시스턴트 데이터를 다루는 컨테이너를 도커로 실행할 때는 데이터 볼륨을 이용했다.
표준 데이터 볼륨은 결국 호스트 머신에 위치하기 때문에, 이러한 방식은 컨테이너의 호스트에 대한
의존성을 강화하는 부작용을 낳는다.
쿠버네티스의 경우 호스트에서 분리할 수 있는 외부 스토리지를 볼륨으로 사용하므로써 이러한 문제를 해결한다.
퍼시스턴트볼륨 혹은 퍼시스턴트볼륨클레임은 이 외부 스토리지의 확보를 위한 쿠버네티스 리소스이다.
퍼시스턴트볼륨은 스토리지 그 자체라고 볼 수 있으며, 반면 볼륨클레임은 추상화된 논리 리소스로, 퍼시스턴트볼륨과는 달리
용량을 필요한 만큼 동적으로 확보할 수 있다.
스토리지클래스는 퍼시스턴트볼륨으로 확보하는 스토리지의 종류를 정의하는 리소스이다.
퍼시스턴트볼륨을 정의하는 매니페스트 파일에서는 스토리지클래스를 지정하는 storageClassName이라는 필드가 존재한다.
실습에서 사용할 스토리지클래스를 위한 매니페스트 파일을 작성하고, 클러스터에 반영한다.
storage-class-ssd.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ssd
annotations:
storageclass.kubernetes.io/is-default-class: "false"
labels:
kubernetes.io/cluster-service: "true"
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
parameters.type 필드에서 해당 스토리지클래스가 '표준' 타입인지 'SSD' 타입인지 설정할 수 있다.
디플로이먼트는 퍼시스턴트 데이터를 갖지 않는 stateless한 애플리케이션을 배포하는데 적절하다.
반면 stateful한 애플리케이션을 배포하는 데에는 statefulSet라는 리소스를 사용한다.
이 statefulset를 이용해서 배포할 mysql-master 서비스의 매니페스트 파일은 다음과 같다.
mysql-master.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql-master
labels:
app: mysql-master
spec:
ports:
- port: 3306
name: mysql
clusterIP: None
selector:
app: mysql-master
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql-master
labels:
app: mysql-master
spec:
serviceName: "mysql-master"
selector:
matchLabels:
app: mysql-master
replicas: 1
template:
metadata:
labels:
app: mysql-master
spec:
terminationGracePeriodSeconds: 60
containers:
- name: mysql
image: gihyodocker/tododb:latest
imagePullPolicy: Always
args:
- "--ignore-db-dir=lost+found"
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: "gihyo"
- name: MYSQL_DATABASE
value: "tododb"
- name: MYSQL_USER
value: "gihyo"
- name: MYSQL_PASSWORD
value: "gihyo"
- name: MYSQL_MASTER
value: "true"
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: ssd
resources:
requests:
storage: 4Gi
volumeClaimTemplates 필드를 정의해서 퍼시스턴트볼륨클레임을 파드마다 자동으로 생성하도록 할 수 있다.
이 덕분에 파드가 요구하는 퍼시스턴트볼륨클레임을 매번 만들지 않아도 된다.
api 서버를 구축하기 위한 매니페스트 파일을 다음과 같이 작성한다.
결합성이 높은 nginx와 API 서버를 하나의 파드로 묶어 배포한다.
todo-api.yaml
apiVersion: v1
kind: Service
metadata:
name: todoapi
labels:
app: todoapi
spec:
selector:
app: todoapi
ports:
- name: http
port: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: todoapi
labels:
name: todoapi
spec:
replicas: 2
selector:
matchLabels:
app: todoapi
template:
metadata:
labels:
app: todoapi
spec:
containers:
- name: nginx
image: gihyodocker/nginx:latest
imagePullPolicy: Always
ports:
- containerPort: 80
env:
- name: WORKER_PROCESSES
value: "2"
- name: WORKER_CONNECTIONS
value: "1024"
- name: LOG_STDOUT
value: "true"
- name: BACKEND_HOST
value: "localhost:8080"
- name: api
image: gihyodocker/todoapi:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
- name: TODO_BIND
value: ":8080"
- name: TODO_MASTER_URL
value: "gihyo:gihyo@tcp(mysql-master:3306)/tododb?parseTime=true"
- name: TODO_SLAVE_URL
value: "gihyo:gihyo@tcp(mysql-slave:3306)/tododb?parseTime=true"
nginx의 경우 api 서버의 호스트를 환경변수로 전달해야 하는데, 같은 파드 안에 api 서버가 존재하므로
localhost로 변수값을 전달해주면 된다.
api의 경우 db 연결을 위한 connection string이 필요하다. 마스터와 슬레이브 각각은 mysql-master, mysql-slave로
네임 레졸루션 되어있기 때문에 이 값을 이용해 connection string을 만들어 환경변수로 전달한다.
$ kubectl get pod -l app=todoapi
NAME READY STATUS RESTARTS AGE
mysql-slave-0 1/1 Running 0 80m
mysql-slave-1 1/1 Running 0 80m
todoapi-cdffbff68-nfbtx 2/2 Running 0 69m
todoapi-cdffbff68-q6xzk 2/2 Running 0 69m
api의 경우 statefulset로 배포한 mysql과는 다르게 파드 이름 뒤에 무작위로 suffix가 붙는 것을 확인할 수 있다.
todo-web.yaml
apiVersion: v1
kind: Service
metadata:
name: todoweb
labels:
app: todoweb
spec:
selector:
app: todoweb
ports:
- name: http
port: 80
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: todoweb
labels:
name: todoweb
spec:
replicas: 2
selector:
matchLabels:
app: todoweb
template:
metadata:
labels:
app: todoweb
spec:
volumes: # 1
- name: assets
emptyDir: {}
containers:
- name: nginx
image: gihyodocker/nginx-nuxt:latest
imagePullPolicy: Always
ports:
- containerPort: 80
env:
- name: WORKER_PROCESSES
value: "2"
- name: WORKER_CONNECTIONS
value: "1024"
- name: LOG_STDOUT
value: "true"
- name: BACKEND_HOST
value: "localhost:3000"
volumeMounts: # 2
- mountPath: /var/www/_nuxt
name: assets
- name: web
image: gihyodocker/todoweb:latest
imagePullPolicy: Always
lifecycle: # 3
postStart:
exec:
command:
- cp
- -R
- /todoweb/.nuxt/dist
- /
ports:
- containerPort: 3000
env:
- name: TODO_API_URL
value: http://todoapi
volumeMounts: # 4
- mountPath: /dist
name: assets
#1 : assets라는 이름의 볼륨 생성. emptyDir로 설정해 파드 단위로 할당되는 가상 볼륨 생성.
#2 & #4 : 1에서 만든 볼륨을 각각의 컨테이너의 어느 위치에 마운트시킬지 설정
#3 : 초기에 이 볼륨은 빈 디렉터리이므로, 컨테이너 시작 직후에 값을 넣어줄 필요가 있음. lifecycle 이벤트를
이용해 이 작업을 실행
인그레스 또한 쿠버네티스 리소스 중 하나이므로 매니페스트 파일로 간단하게 인그레스를 클러스터에 반영할 수 있다.
ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress
spec:
rules:
- http:
paths:
- path: /*
backend:
serviceName: todoweb
servicePort: 80
어떤 서비스의 어떤 포트를 외부에 노출시킬 지에 대한 것을 rules 필드 내에서 정의할 수 있고, 이를 클러스터에 적용한 후 할당받은 글로벌 IP 주소는
todoweb으로 연결된다.
GCP 콘솔을 보면 지금껏 만든 서비스 및 인그레스를 확인할 수 있고, 인그레스에 할당되어 있는 엔드포인트로 접속해보면 정상적으로 투두리스트가 잘 뜨는 것을 확인할 수 있다.