최근 많은 기업들이 어플리케이션 아키텍처로서, 기존 모노리스 아키텍처 방식을 버리고 마이크로 서비스 아키텍처를 도입하면서 자연스럽게 소프트웨어의 운영 방식에도 많은 변화가 일어났습니다. 수많은 서비스들을 빠른 시간내에 배포하는 작업이 빈번히 요구되는 운영 환경 조건들을 충족해줄 수 있는 컨테이너 기술들이 인기가 많아짐에 따라 자연스레 분산 컨테이너 환경을 자동화할 수 있도록 돕는 대표적 오케스트레이션 툴인 Kubernetes를 점차 활용하고 있는 추세입니다.

Kubernetes의 스케줄링, 스케일링, 자가 회복, 리소스 관리 등 어플리케이션 서비스의 운영 자동화를 위한 컨테이너 오케스트레이션 기술이 주는 이점은 무궁무진합니다. 하지만 기존 On-Premise 환경에 구동되고 있는 어플리케이션, 외부 솔루션들을 Kubernetes로 이전하기 위한 작업은 쉽지 않습니다. 무상태(stateless)의 어플리케이션의 경우 단순한 Kubernetes의 리소스 생성만으로 쉽게 운영 할 수 있지만, 분산 캐시, 데이터베이스, 모니터링 시스템과 같은 유상태(stateful)의 어플리케이션의 경우에는 Kubernetes의 기본 기능만으로는 한계가 있습니다. 또한 어플리케이션의 복잡도에 따라서 해당 도메인에 특화된 운영자가 반드시 필요합니다. 이런 운영상의 어려움들은 Kubernetes가 주는 자동화의 이점을 제대로 활용하기 힘듭니다.

Operator는 CoreOS에서 개발한 프레임워크로, 복잡한 어플리케이션의 워크로드들을 추상화된 형태로 Kubernetes 환경에서 운영할 수 있게 해주어 어플리케이션의 운영 자동화에 대한 한계를 극복해줍니다. 이는 Kubernetes의 Custom Resource(CR)를 사용하여 어플리케이션을 패키징화함으로써 해결합니다. Kubernetes가 제공되는 기본 기능 이상의 작업을 자동화하기 위해 CR의 상태를 관리하는 컨트롤러 코드를 구현하는 방법을 사용하는데, Operator는 이런 컨트롤러를 개발자가 쉽게 구현할 수 있도록 Boilerplate 코드를 제공합니다.

Operator에 관한 보다 자세한 내용은 아래 링크를 참고하세요.

ARCUS Operator

ARCUS는 On-Premise 환경 뿐만 아니라 Kubernetes 위에서도 운영하기 어려운 복잡한 어플리케이션들 중 하나입니다. 스케일링 작업시 캐시 서비스 업데이트가 요구되고, 데이터 유실을 방지하기 위해 마이그레이션 작업이 필요합니다. 또한 롤링 업데이트시 복제 데이터를 유지하기 위해 복제 그룹 구조를 고려하여 업데이트를 수행해야 합니다. 이러한 작업들은 Kubernetes의 기본 기능만으로는 수행할 수 없고, 직접 수동으로 진행해야 하는 불편함이 있습니다. 이를 자동화하기 위해서는 운영에 필요한 로직들을 코드의 형태로 구현하고, ARCUS 캐시 클러스터의 상태가 변할 때마다 미리 구현해둔 로직을 실행할 수 있어야 합니다.

ARCUS Operator에는 ARCUS의 운영 로직을 코드 형태로 수행하는 자동화된 컨트롤러가 존재합니다. 사용자가 ArcusMemcached CR을 통해 캐시 클러스터가 배포될 스펙을 정의하고, Kubernetes에 배포하면 컨트롤러가 CR의 상태를 감시하고, 상태에 따른 로직을 수행하게 될 것입니다.

ARCUS Operator의 내부 동작을 예로 들면 아래와 같습니다.

  1. CR의 스펙과 현재 캐시 클러스터의 상태를 비교
  2. CR의 스펙에 따른 운영 로직을 실행
  3. CR의 스펙을 만족시킬 수 있도록 상태를 업데이트

만약 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
  1. ArcusMemcached CR의 replicas(5) 값과 현재 test 캐시 서비스에서 컨테이너로 구동되고 있는 캐시 노드 대수(3)를 비교.
  2. test 캐시 서비스의 캐시 노드 대수를 ArcusMemcached CR의 replicas(5) 값 만큼 변경하기 위해 ZooKeeper에 연결하여 test 캐시 서비스에 노드를 2대 추가 등록.
  3. test 캐시 서비스의 캐시 노드 컨테이너 2대를 추가 생성하도록 상태 업데이트 (Statefulset 리소스의 replicas를 5로 업데이트).

사용 방법

ARCUS Operator를 Kubernetes에 배포하여 캐시 클러스터를 생성하고, 마지막으로 Kubernetes 환경에서 동작하는 ARCUS를 사용하는 어플리케이션을 직접 만들어 테스트를 해보겠습니다.

ROLE-BASED ACCESS CONTROL (RBAC) 생성

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

Custom Resource Definition(ArcusMemcached) 생성

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

ARCUS Operator 생성

지금까지의 과정을 마쳤다면, 이제부터 다른 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

ArcusMemcached(Custom Resource) 생성

마지막으로 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에서 제공될 추가 기능

  • 운영 중 Dynamic Configuration을 지원하는 ARCUS ZooKeeper의 CR 지원
  • 스케일링시 데이터 유실 방지를 위한 마이그레이션 자동화 지원
  • 장애 복구를 위한 복제 기능 지원
  • 리소스 사용량에 따른 자동 스케일링 지원
  • 리소스 패키징을 위한 Helm Chart 지원

아쉽게도 ARCUS Operator는 GitHub에 공개되어 있지 않아, Enterprise Edition을 구독한 고객에게만 제공되고 있습니다. 추가로 ARCUS Operator와 관련한 궁금한 점이나 문의 사항이 있다면 support@jam2in.com 으로 문의 바랍니다.

0개의 댓글