최근 많은 기업들이 어플리케이션 아키텍처로서, 기존 모노리스 아키텍처 방식을 버리고 마이크로 서비스 아키텍처를 도입하면서 자연스럽게 소프트웨어의 운영 방식에도 많은 변화가 일어났습니다. 수많은 서비스들을 빠른 시간내에 배포하는 작업이 빈번히 요구되는 운영 환경 조건들을 충족해줄 수 있는 컨테이너 기술들이 인기가 많아짐에 따라 자연스레 분산 컨테이너 환경을 자동화할 수 있도록 돕는 대표적 오케스트레이션 툴인 Kubernetes를 점차 활용하고 있는 추세입니다.
Kubernetes의 스케줄링, 스케일링, 자가 회복, 리소스 관리 등 어플리케이션 서비스의 운영 자동화를 위한 컨테이너 오케스트레이션 기술이 주는 이점은 무궁무진합니다. 하지만 기존 On-Premise 환경에 구동되고 있는 어플리케이션, 외부 솔루션들을 Kubernetes로 이전하기 위한 작업은 쉽지 않습니다. 무상태(stateless)의 어플리케이션의 경우 단순한 Kubernetes의 리소스 생성만으로 쉽게 운영 할 수 있지만, 분산 캐시, 데이터베이스, 모니터링 시스템과 같은 유상태(stateful)의 어플리케이션의 경우에는 Kubernetes의 기본 기능만으로는 한계가 있습니다. 또한 어플리케이션의 복잡도에 따라서 해당 도메인에 특화된 운영자가 반드시 필요합니다. 이런 운영상의 어려움들은 Kubernetes가 주는 자동화의 이점을 제대로 활용하기 힘듭니다.
Operator는 CoreOS에서 개발한 프레임워크로, 복잡한 어플리케이션의 워크로드들을 추상화된 형태로 Kubernetes 환경에서 운영할 수 있게 해주어 어플리케이션의 운영 자동화에 대한 한계를 극복해줍니다. 이는 Kubernetes의 Custom Resource(CR)를 사용하여 어플리케이션을 패키징화함으로써 해결합니다. Kubernetes가 제공되는 기본 기능 이상의 작업을 자동화하기 위해 CR의 상태를 관리하는 컨트롤러 코드를 구현하는 방법을 사용하는데, Operator는 이런 컨트롤러를 개발자가 쉽게 구현할 수 있도록 Boilerplate 코드를 제공합니다.
Operator에 관한 보다 자세한 내용은 아래 링크를 참고하세요.
ARCUS는 On-Premise 환경 뿐만 아니라 Kubernetes 위에서도 운영하기 어려운 복잡한 어플리케이션들 중 하나입니다. 스케일링 작업시 캐시 서비스 업데이트가 요구되고, 데이터 유실을 방지하기 위해 마이그레이션 작업이 필요합니다. 또한 롤링 업데이트시 복제 데이터를 유지하기 위해 복제 그룹 구조를 고려하여 업데이트를 수행해야 합니다. 이러한 작업들은 Kubernetes의 기본 기능만으로는 수행할 수 없고, 직접 수동으로 진행해야 하는 불편함이 있습니다. 이를 자동화하기 위해서는 운영에 필요한 로직들을 코드의 형태로 구현하고, ARCUS 캐시 클러스터의 상태가 변할 때마다 미리 구현해둔 로직을 실행할 수 있어야 합니다.
ARCUS Operator에는 ARCUS의 운영 로직을 코드 형태로 수행하는 자동화된 컨트롤러가 존재합니다. 사용자가 ArcusMemcached CR을 통해 캐시 클러스터가 배포될 스펙을 정의하고, Kubernetes에 배포하면 컨트롤러가 CR의 상태를 감시하고, 상태에 따른 로직을 수행하게 될 것입니다.
ARCUS Operator의 내부 동작을 예로 들면 아래와 같습니다.
만약 ARCUS Operator에서 제공되는 아래의 ArcusMemcached CR을 사용하여, 캐시 클러스터의 캐시 노드 대수를 3에서 5로 늘려 스케일링한다고 가정하면, ARCUS Operator의 컨트롤러는 아래와 같이 동작이 될 것입니다.
$ cat arcus_memcached.yaml
apiVersion: jam2in.com/v1
kind: ArcusMemcached
metadata:
name: test
spec:
replicas: 5 # 기존에는 3이었던 상태
serviceCode: test
zookeeperServers:
- 1.2.3.4:2181
configuration:
memlimit: 100
connections: 1000
threads: 10
image:
name: jam2in/arcus-memcached:1.11.7
pullPolicy: Always
$ kubectl apply -f arcus_memcached.yaml
test
캐시 서비스에서 컨테이너로 구동되고 있는 캐시 노드 대수(3)를 비교.test
캐시 서비스의 캐시 노드 대수를 ArcusMemcached CR의 replicas(5) 값 만큼 변경하기 위해 ZooKeeper에 연결하여 test
캐시 서비스에 노드를 2대 추가 등록.test
캐시 서비스의 캐시 노드 컨테이너 2대를 추가 생성하도록 상태 업데이트 (Statefulset 리소스의 replicas를 5로 업데이트).ARCUS Operator를 Kubernetes에 배포하여 캐시 클러스터를 생성하고, 마지막으로 Kubernetes 환경에서 동작하는 ARCUS를 사용하는 어플리케이션을 직접 만들어 테스트를 해보겠습니다.
ARCUS Operator 내부에는 CR들의 상태를 관리하는 컨트롤러들이 존재합니다. 컨트롤러는 CR의 상태를 감시하고, 이들의 원하는 상태를 만족시키기 위해 Kubernetes의 Built-in 리소스들을 Kubernetes API를 사용하여 생성하고 관리합니다. 이를 위해 ARCUS Operator의 Kubernetes API 사용 권한에 대한 설정이 필요합니다. 아래와 같이 kubectl 명령어를 사용하여 ARCUS Operator의 서비스 계정과 권한을 생성합니다.
$ cat arcus_rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: arcus-operator
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: null
name: arcus-operator
rules:
- apiGroups:
- ""
- apps
resources:
- pods
- services
- configmaps
- statefulsets
verbs:
- get
- list
- watch
- create
- update
- apiGroups:
- policy
resources:
- poddisruptionbudgets
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- monitoring.coreos.com
resources:
- servicemonitors
verbs:
- get
- create
- apiGroups:
- jam2in.com
resources:
- memcacheds
- memcacheds/status
verbs:
- get
- list
- watch
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: arcus-operator
subjects:
- kind: ServiceAccount
name: arcus-operator
namespace: default
roleRef:
kind: ClusterRole
name: arcus-operator
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: arcus-operator
subjects:
- kind: ServiceAccount
name: arcus-operator
namespace: default
roleRef:
kind: ClusterRole
name: arcus-operator
apiGroup: rbac.authorization.k8s.io
$ kubectl apply -f arcus_rbac.yaml
serviceaccount/arcus-operator created
clusterrole.rbac.authorization.k8s.io/arcus-operator created
clusterrolebinding.rbac.authorization.k8s.io/arcus-operator created
ARCUS 캐시 클러스터의 Custom Resource Definition(CRD)를 생성합니다. 생성을 완료하면, 사용자는 다른 Built-in 리소스와 같이 kubectl 명령어 또는 API를 사용하여 ArcusMemcached CR을 생성하고 관리할 수 있게 됩니다. CRD에는 CR의 API 그룹, 리소스명, 상태, 유효성 정보들이 포함되어있습니다.
$ cat arcus_memcached_crd.yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: memcacheds.jam2in.com
spec:
group: jam2in.com
names:
kind: ArcusMemcached
listKind: ArcusMemcachedList
plural: memcacheds
singular: memcached
shortNames:
- mc
scope: Namespaced
version: v1
subresources:
status: {}
additionalPrinterColumns:
- name: Age
type: date
JSONPath: .metadata.creationTimestamp
- name: Ready
type: string
description: The number memcached nodes ready
JSONPath: .status.ready
- name: Container
type: string
description: The memcached container
JSONPath: .status.container
- name: Image
type: string
description: The memcached image
JSONPath: .status.image
- name: Message
type: string
description: The memcached error message
JSONPath: .status.message
$ kubectl apply -f arcus_memcached_crd.yaml
customresourcedefinition.apiextensions.k8s.io/memcacheds.jam2in.com created
$ kubectl get crd
NAME CREATED AT
memcacheds.jam2in.com 2020-08-26T02:02:25Z
지금까지의 과정을 마쳤다면, 이제부터 다른 Kubernetes 리소스들처럼 kubectl 명령을 사용하여 ArcusMemcached CR을 생성할 수 있습니다. 하지만 이 상태에서 CR을 생성한다면, ARCUS 캐시 클러스터가 Kubernetes에 컨테이너로 배포되지 않고, 아무런 동작도 수행되지 않을 것입니다. ArcusMemcached CR의 상태를 감시하고 이를 컨트롤 할 수 있는 컨테이너가 아직 생성되지 않았기 때문입니다.
이를 위해 ARCUS Operator의 Deployment를 생성해줍니다. 아래의 Deployment spec에서 replicas는 생성될 Pod의 개수를 의미합니다. replicas가 N개 이상으로 설정된 경우, 배포된 N개의 Pod 중 하나가 Master 역할이 부여되고, 나머지는 Stand-by 상태로 유지하게 됩니다. Master가 중단되면 Stand-by 상태에 있던 Pod 중 하나가 Master 역할을 가지게 될 것입니다.
$ cat arcus_operator.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: arcus-operator
spec:
replicas: 3
selector:
matchLabels:
name: arcus-operator
template:
metadata:
labels:
name: arcus-operator
spec:
serviceAccountName: arcus-operator
containers:
- name: arcus-operator
image: jam2in/arcus-operator:0.0.6
command:
- arcus-operator
imagePullPolicy: Always
env:
- name: WATCH_NAMESPACE
value: ""
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: OPERATOR_NAME
value: "arcus-operator"
$ kubectl apply arcus_operator.yaml
deployment.apps/arcus-operator created
$ kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
arcus-operator 3 3 3 3 13s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
arcus-operator-7647cf945-2gt4v 1/1 Running 0 1m
arcus-operator-7647cf945-8mhgn 1/1 Running 0 1m
arcus-operator-7647cf945-wqdc9 1/1 Running 0 1m
마지막으로 ARCUS 캐시 클러스터를 Kubernetes에 배포하기 위해 ArcusMemcached CR을 생성합니다. CR 생성을 완료하면 test
서비스코드를 가진 캐시 서버가 총 3대로 구성될 것입니다.
$ cat arcus_memcached.yaml
apiVersion: jam2in.com/v1
kind: ArcusMemcached
metadata:
name: test
spec:
# 생성할 캐시 노드의 컨테이너 대수
replicas: 3
# 서비스 코드
serviceCode: test
# 캐시 클러스터 관리를 위한 ZooKeeper 서버 주소
zookeeperServers:
- 1.2.3.4:2181
# 캐시 프로세스의 구동 옵션
configuration:
memlimit: 100
connections: 1000
threads: 10
# ARCUS Memcached의 Docker 이미지 정보
image:
name: jam2in/arcus-memcached:1.11.7
pullPolicy: Always
$ kubectl apply -f arcus_memcached.yaml
arcusmemcached.jam2in.com/jam2in created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
arcus-operator-7647cf945-9hlqp 1/1 Running 0 59m
arcus-operator-7647cf945-tc6ld 1/1 Running 0 59m
arcus-operator-7647cf945-z4d74 1/1 Running 0 51m
test-arcus-mc-0 1/1 Running 0 16m
test-arcus-mc-1 1/1 Running 0 16m
test-arcus-mc-2 1/1 Running 0 16m
Kubernetes 상에서 컨테이너로 운영되고 있는 ARCUS 캐시 클러스터의 동작을 확인하기 위해 어플리케이션을 만들어 테스트를 진행할 것입니다. 이를 위해 아래의 Deployment yaml을 작성하여 배포합니다. 컨테이너 환경변수인 ARCUS_ADDRESS
는 ZooKeeper 주소로 설정하고, ARCUS_SERVICE_CODE
는 방금 전 ArcusMemcached CR을 생성했던 서비스 코드로 설정합니다.
$ cat application.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: arcus-application-sample
labels:
app: arcus-application-sample
spec:
replicas: 1
selector:
matchLabels:
app: arcus-application-sample
template:
metadata:
labels:
app: arcus-application-sample
spec:
containers:
- name: arcus-application-sample
image: jam2in/arcus-application-sample:0.0.1
ports:
- containerPort: 80
env:
- name: ARCUS_ADDRESS
value: "10.34.33.81:7189"
- name: ARCUS_SERVICE_CODE
value: "test"
---
apiVersion: v1
kind: Service
metadata:
name: arcus-application-sample
labels:
app: arcus-application-sample
spec:
ports:
- port: 8080
protocol: TCP
selector:
app: arcus-application-sample
$ kubectl apply -f application.yaml
deployment.apps/arcus-application-sample created
service/arcus-application-sample created
테스트를 위한 어플리케이션 코드는 링크를 참고하시면 됩니다. 어플리케이션에 요청이 들어오면 kubernetes:arcus
key를 통해 캐시 서버에 아이템을 조회하고, 아이템이 존재하지 않는다면 캐시 서버에 hello arcus!
이라는 문자열을 저장합니다. 만약 캐시 아이템이 존재하면 hello arcus!
, 존재하지 않는다면 hello spring!
을 응답을 보내도록 구현하였습니다.
어플리케이션이 Running 상태임을 확인한 이후, 어플리케이션의 서비스로 요청을 보내 테스트를 진행합니다.
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
arcus-application-sample-85bf8d5d6d-hp67n 1/1 Running 0 18m
arcus-operator-7647cf945-9hlqp 1/1 Running 0 1h
arcus-operator-7647cf945-tc6ld 1/1 Running 0 1h
arcus-operator-7647cf945-z4d74 1/1 Running 0 1h
test-arcus-mc-0 1/1 Running 0 43m
test-arcus-mc-1 1/1 Running 0 43m
test-arcus-mc-2 1/1 Running 0 42m
$ kubectl get svc arcus-application-sample
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
arcus-application-sample ClusterIP 10.108.36.196 <none> 8080/TCP 18m
$ curl 10.108.36.196:8080
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ARCUS TEST</title>
</head>
<body>
<h1>hello spring!</h1>
</body>
</html>
처음에는 kubernetes:arcus key
를 가진 아이템이 어느 캐시 서버에도 존재하지 않아 hello spring!
이라는 응답을 받았습니다. 해당 요청이 성공적이었다면 어플리케이션 로직에 의해 hello arcus!
라는 문자열을 캐시 서버에 저장했을 것입니다. 여기서 한번 더 요청을 보내 캐싱이 제대로 이루어졌는지 확인합니다.
$ curl 10.108.36.196:8080
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ARCUS TEST</title>
</head>
<body>
<h1>hello arcus!</h1>
</body>
</html>
어플리케이션에 설정된 캐시 아이템의 TTL은 10초입니다. 10초 후 다시 요청을 보내 캐시 아이템이 정상적으로 제거되었는지 확인합니다.
$ curl 10.108.36.196:8080
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ARCUS TEST</title>
</head>
<body>
<h1>hello spring!</h1>
</body>
</html>
ARCUS Operator는 작년부터 Kubernetes를 도입한 기업에서 현재까지 실서비스로 안정적으로 운영되고 있습니다. 하지만 현재까지 개발된 ARCUS Operator는 ARCUS 캐시 클러스터의 기본 운영을 위한 최소 기능만 제공되고 있습니다. Kubernetes 환경에서 ARCUS 캐시 클러스터가 더욱 안정적으로 운영되기 위해서는 ARCUS Enterprise 버전에서 제공되는 기능인 복제, 마이그레이션 기능이 ARCUS Operator에서도 제공되어야 할 것입니다. 앞으로의 계획은 ARCUS Operator의 운영 안정성과, 자동화를 아래의 추가 기능들을 통해 한층 더 향상하는 작업을 진행하는 것입니다.
추후 ARCUS Operator에서 제공될 추가 기능
아쉽게도 ARCUS Operator는 GitHub에 공개되어 있지 않아, Enterprise Edition을 구독한 고객에게만 제공되고 있습니다. 추가로 ARCUS Operator와 관련한 궁금한 점이나 문의 사항이 있다면 support@jam2in.com 으로 문의 바랍니다.