쿠버네티스의 서비스 앱 만들기
포스트
취소

쿠버네티스의 서비스 앱 만들기

개요

작년에 처음 구현하고자 했던, ERP 시스템에서 발전이 되어서 서버리스 서비스를 제공 하고자 변경이 되면서 오픈스택이 아닌 쿠버네티스를 활용하여 CaaS(Containters As A Service)를 제공하고자 했습니다. 그래서 학과 서버 컴퓨터인 쿠버네티스에 CaaS를 제공하기 위해서 기본적인 기능을 구현 해야 했고, 직접 쿠버네티스의 API Server에 접근해서 동적으로 Deployment, Pods등 다양한걸 추가하고, 리소스를 확인 하는 기능을 구현해야 했습니다. 이러한 기능을 구현하기 위해서는 기존에 제공해주는 서비스는 힘들어 직접 구현 하게 되었습니다.

쿠버네티스 어떻게 동작하는가?

쿠버네티스 동작원리
쿠버네티스 클러스터 구성 요소

kubeadm으로 설치 하게 된다면 간단하게 Master Node(이하 마스터 노드)와 Worker Node(워커 노드) 선택하게 됩니다. 마스터 노드는 쿠버네티스 시스템을 관리하고 통제하는 노드이며, 워커 노드는 실제 컨테이너가 올라가서 동작하는 노드 입니다.

마스터 노드는 실제 각 워커 노드를 관리, 팟 할당 위치, 여러가지의 yaml 데이터를 저장하거나 분할하여 처리를 합니다. 마스터 노드와 워커 노드 자체는 PC의 단위가 아닌 프로그램 단위 이기 때문에 1대의 PC에서 마스터, 워커 모두 둘 다 동작 할 수 있다.

kubectl로 “kubectl apply -f resource.yaml” 와 같이 kubectl란 프로그램으로 명령을 날리게 되면 API Server로 요청을 하고 결과를 나타내는 프로그램이다. 그렇기 때문에 굳이 kubectl을 조작하여 무언가를 실행하는것 보다는 API Server로 직접 전송하여 조작을 할 수 있다면 더 많은 조작이 가능하다. 그래서 위의 그림에서 모든 행위는 API Server을 통하여 데이터를 요청하고, 시스템을 관리한다.

흔히 AWS에서 제공해주는 EKS(Elastic Kubernetes Service)는 해당 되는 마스터 노드, 컨트롤 플레인을 구성하지 않고 워커 노드만 구성하게 된다면 컨테이너를 관리해 주는 서비스이다. 워커 노드에 마스터 노드를 설치하게 된다면 온전한 네트워크 트래픽이나 특정 워커 노드가 문제가 생겼을 때 재시작 하고자 해도 마스터 노드 때문에 불가능 할 수 있다. 그래서 EKS가 1달에 7~8만원 나오지만 저렴할 수 있다.

어떻게 쿠버네티스 API Server에 접근 했는가?

쿠버네티스 Client에서 제공해주는 쿠버네티스를 접근 하기 위한 라이브러리가 있다. 해당 라이브러리를 활용하여 API Server에 접속 했다. 로컬 PC 에서 쿠버네티스로 접속 하는것은 따로 권한이 필요 없지만 쿠버네티스 안에서 Pods이나 Deployment를 생성하기 위해서는 권한이 필요하다. 그래서 RBAC(Role Base Access Control)를 통해 Pods에 대한 권한을 할당 한 뒤, Cluster안에서 여러개의 팟을 실행 할 수 있도록 해야했다. 그래서 하나의 Pod이 모든 노드, 모든 Namespace에 대한 권한을 얻기 위해서 ClusterRole로 만들었다. 만약 DemonSet이라면 ClusterRole이 아닌 Role로 해도 괜찮지만 ClusterRole로 하였다.

ClusterRole이면 ServiceAccount와 연결하기 위해 ClusterRoleBinding을 통해 연결 했다. 쿠버네티스 안에서 API Server을 접근 하고 편집을 하기 위해서는 Role이라는 권한 체계가 있음을 알게 되었고 공부하게 되었다.

전체적인 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
kind: ServiceAccount
apiVersion: v1
metadata:
  name: aswcloud-k8s-sa
---
kind: ClusterRole # Role of kubernetes
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: aswcloud-k8s-clusterrole
rules:
  - apiGroups: [""] # rules on persistentvolumes
    resources: ["persistentvolumes", "persistentvolumeclaims", "services", "deployments", "namespaces"]
    verbs: ["get", "list", "watch", "create", "delete"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: aswcloud-k8s-rolebinding
subjects:
  - kind: ServiceAccount
    name: aswcloud-k8s-sa
    namespace: aswcloud
roleRef: # binding cluster role to service account
  kind: ClusterRole
  name: aswcloud-k8s-clusterrole # name defined in clusterRole
  apiGroup: rbac.authorization.k8s.io

그리고 Deployment에서 권한을 설정하기 위해서 serviceAccountName을 지정해야 하므로 spec.template.sepc.serviceAccountName 값을 aswcloud-k8s-sa 로 지정했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: apps/v1
kind: Deployment
metadata:
  name: aswcloud-k8s-deployment
  labels:
    app: aswcloud-k8s
spec:
  replicas: 1
  selector:
    matchLabels:
      app: aswcloud-k8s
  template:
    metadata:
      labels:
        app: aswcloud-k8s
    spec:
      serviceAccountName: aswcloud-k8s-sa
      containers:
        - name: server
          image: aoikazto/k8s-server:0.1.9
          ports:
            - containerPort: 8000
          env:
            - name: LOCALHOST
              value: 'False'
ArgoCD 배포 모습PostMan의 전송 모습
argocdpostman


배포하여 테스트한 모습

개발.. 어디까지 진행이 되었는가?

argocd
실제 구현하고 있는 내부 모습

iOS, Desktop App의 경우에서는 서버와 통신하여 회원가입, 로그인까지 구현을 하였다. 로그인에 실패한 경우 실패하였다고 알려주고 있으며, 성공한 경우는 JWT를 이용한 토큰 값과, 사용자의 고유 번호를 반환 한다.

로그인 실패로그인 성공
failloginsuccess

iOS와 Server-comm끼리의 통신에서 정상적으로 로그인, 회원가입이 되고 있다. 그리고 server-k8s가 정상적으로 동작하는지 PoC를 거치고 있는 중이다. server-k8s가 범용성을 가지기 위해서 데이터를 요청 할 때, 데이터를 기반으로 text/template로 바로 돌리는 형식으로 하여 k8s와 연동 되게끔 하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
message service_port { 
    string name = 1;
    int32 target_port = 2;
    int32 node_port = 3;
    int32 container_port = 4;
}

message service { 
    string namespace = 1;

    string name = 2;
    string type = 3;
    // deployment template_name
    string template_name = 4;
    repeated service_port ports = 5;
}

Protocol Buffer에서 위 처럼 service추가 하기 위한 idl 코드를 작성 하고 실제 서버에서는 해당 데이터를 받게 된다면 Json으로 Mashal 한 뒤 원하는 객체로 UnMashal하고 text/template로 변환 하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
  name: { { .Name } }
spec:
  type: { { .Type } }
  selector:
    app: { { .TemplateName } }
  ports: { { range .Ports } }
    - name: { { .Name } }
      port: { { .ContainerPort } } 
      targetPort: { { .TargetPort } } { { if not (eq .NodePort 0) } }
      nodePort: { { .NodePort } } { { end } } { { end } }
1
2
3
4
5
6
7
8
9
10
11
12
13
type ServicePortTemplate struct {
	Name          string `json: "name"`
	TargetPort    int32  `json: "targetPort"`
	NodePort      int32  `json: "nodePort"`
	ContainerPort int32  `json: "containerPort"`
}

type ServiceTemplate struct {
	Name         string                `json: "name"`
	Type         string                `json: "type"`
	TemplateName string                `json: "templateName"`
	Ports        []ServicePortTemplate `json: "ports"`
}

와! 이것이! 중간 과정! 대단해!

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

Argo CD와 Github Action을 활용한 GitOps 시스템 구축

동의대학교 공지사항 대시보드 구현