kubernetes 백업 위한 velero x minio docker / kubernetes


쿠버네티스 환경에서 개발을 하다 보면 특정 환경변수를 입력하여 배포 하는 일이 자주 발생한다.
배포때마다 매번 입력하는 상황이 반복 될 수 있는데 이를 피하고자 백업 후에 복원을 하는 방법을 구축했다.



kubernetes 백업의 종류

백업은 노드 레벨과 어플리케이션 레벨로 나눌 수 있다.
노드 레벨은 말그대로 클러스터링 환경을 백업해두고 재구축 하는 것이고,
어플리케이션 레벨은 배포된 리소스들의 백업이다.

마스터 노드 백업
kubernetes 환경을 복원 및 재구축시 진행
etcd snapshot 저장 / kubeadm config 설정 백업


어플리케이션 레벨의 백업 (controller, resource 등등)
Pods, deployment, replicaset 등의 controller 및 리소스 등을 백업하여, 특정 시점의 배포된 상태로 복원한다.

노드 레벨의 백업은 설정 파일 등의 백업 후 재사용으로 환경을 구축해내는 것으로 진행 가능하다.
다행이 어플리케이션 레벨의 백업은 이미 오픈 소스가 있어서 손쉽게 가능하다.

오픈 소스

velero 를 통해 백업 및 복원 기능을 제공할 수 있으며, velero에서 연동 가능한 스토리지로 minio가 소개되어 있다.

이 포스팅에서는 어플리케이션 레벨의 백업에 대한 내용을 진행하겠다.



velero x minio 구축

100

velero

Kubernetes 클러스터 리소스 및 영구 볼륨을 백업 및 복원 할 수있는 도구.

AWS, GCP, Azure 와 연동하여 구축이 가능.

on-premise 환경에선 minio와 호환하여 구축 가능


MinIO

고성능 분산 객체 스토리지 시스템

MinIO는 처음부터 프라이빗 클라우드 객체 스토리지의 표준으로 설계

성능과 확장성이 뛰어나고 가벼운 클라우드 서버 구축 가능



구축에 앞서 velero의 경우 인증을 위한 secret 생성을 먼저 진행해야 한다. 
이 secret에 minio와 연동 위한 인증키 정보를 담아야 minio와 연동하여 백업 데이터를 저장 가능하다.

1
kubectl create secret generic cloud-credentials --namespace <VELERO_NAMESPACE> --from-file cloud=<FILE_NAME>
cs

파일 이름은 minio 구축 시에 입력하는 accessKey와 secretKey 정보가 담긴 파일 경로가 들어가면 된다.
velero 설치시에 설정 되어 있는 기본 secret 이름이 cloud-credentials 이며 참조하는 키 이름이 cloud 인데
따로 설정을 수정하지 않는다면 고정으로 입력해주면 된다.


minio 스토리지 구축

minio 페이지에서 다양한 설치 방법을 제공하고 있다.

Standalone, Distributed 두 가지 설치 방법이 있으며, 하나의 노드에 대해 설치를 진행할 때는 standalone으로 진행하고, 클러스터 환경에선 distributed 로 진행하면 된다.

distributed는 최소 4대의 노드가 필요하며, 쿠버네티스 설치에 대해서는 두 가지로 설치를 진행이 가능하다.

statefulset 설치를 위한 yaml 파일을 생성하여 배포하는 방법과 helm 차트에서 설치가 가능하다.

accessKey와 secretKey에는 특수문자를 넣으면 안된다.



필요한 입력 값을 기입하면 yaml 을 생성해준다. 파일로 저장하여 배포하면 간단히 설치 가능하다.

helm 차트에 등록되어 있는 minio를 설치 가능하다.

helm 차트를 이용할 경우 access_key, secret_key 등에 대한 옵션을 추가해서 설치를 진행해야 한다.


on-premise 환경 진행이므로 PV, PVC 를 미리 생성 후 연결하여 배포를 진행해야한다.

hostPath가 아닌 local-storage를 연동해야 분산환경에서 각 노드간에 저장된 내용 공유가 가능하다.


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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
apiVersion: v1
kind: PersistentVolume
metadata:
  name: minio-local-volume
  namespace: velero
spec:
  capacity:
    storage: 5G
  accessModes:
  - ReadWriteMany
  volumeMode: Filesystem
  storageClassName: "local-storage"
  local:
    path: /data/minio
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - "node-dev1"
          - "node-dev2"
          - "node-dev3"
          - "node-dev4"
          - "node-dev5"
          - "node-dev6"
  claimRef:
    namespace: velero
    name: minio-local-volume-claim
 
---
 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: minio-local-volume-claim
  namespace: velero
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 5G
  storageClassName: local-storage
  volumeMode: Filesystem
cs

PVC 생성 후 생성한 yaml 파일로 minio 배포를 진행

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
apiVersion: v1
kind: Service
metadata:
  name: minio
  namespace: velero
  labels:
    app: minio
spec:
  clusterIP: None
  ports:
    - port: 9000
      name: minio
  selector:
    app: minio
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: minio
  namespace: velero
spec:
  serviceName: minio
  replicas: 4
  template:
    metadata:
      labels:
        app: minio
    spec:
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: minio-local-volume-claim
      containers:
      - name: minio
        env:
        - name: MINIO_ACCESS_KEY                  ## velero 연동 시 사용되는 정보
          value: "minio"
        - name: MINIO_SECRET_KEY
          value: "minio123"
        image: minio/minio:latest
        args:
        - server 
        - http://minio-{0...3}.minio.velero.svc.cluster.local/data   # 4대의 분산 노드와 통신하기 위한 service 내부 도메인 / 도메인 뒤에 url 경로는 volume 매핑되는 폴더 경로
        ports:
        - containerPort: 9000
        volumeMounts:
        - name: data
          mountPath: /data
 
---
apiVersion: v1
kind: Service
metadata:
  name: minio-service
  namespace: velero
spec:
  type: NodePort
  ports:
    - port: 9000
      targetPort: 9000
      protocol: TCP
      nodePort: 32099
  selector:
    app: minio
cs

볼륨 경로가 잘못되지 않는 상황이면 대부분 배포에 문제는 발생하지 않는다.

storage 특성 때문인지 처음 설치 후에 replicas 수가 조정되면 초기화 에러 및 상태 공유가 안되는 이슈가 발생한다.

처음 구축 시에 사용할 노드에 대해 설계 완료 후 사용 권장한다.

이 후 설치한 <url>:32099로 접속하게 되면 화면이 출력되고 설장한 accessKey와 secretKey로 로그인하면 된다.



velero 서버 구축

velero 사용을 위해서는 velero 서버 구축이 필요하며, 클라이언트 설치가 필요하다.

release 정보 페이지 : https://github.com/vmware-tanzu/velero/releases

1
2
3
wget <RELEASE-TARBALL-URL>
tar -xvf <RELEASE-TARBALL-NAME>.tar.gz -/dir/to/extract/to
cp /dir/to/extract/to/velero /usr/local/bin
cs

클라이언트 설치 완료 후 minio 와 연동 위한 인증키 정보 파일로 부터 secret 을 생성

1
2
3
[default]
aws_access_key_id = minio
aws_secret_access_key = minio123
cs

velero 서버 설치를 진행. --backup-location-config 옵션 값은 minio 연동 위한 값으로 모두 넣어줘야 한다. 

또한 velero install 전에 --bucket 옵션으로 주어지는 bucket이 minio에 생성되어 있어야한다.\

1
2
3
4
5
6
7
8
velero install \
    --provider aws \
    --bucket velero \
    --secret-file ./cloud-credentials \
    --use-volume-snapshots=false \
    --backup-location-config region=minio,s3ForcePathStyle="true",s3Url=http://minio.velero.svc:9000,publicUrl=<minio가 설치된 서버:포트번호>
    # 1.2.0 버전 업되면서 install 옵션이 추가
    --plugins velero/velero-plugin-for-aws:v1.0.0,velero/velero-plugin-for-microsoft-azure:v1.0.0,velero/velero-plugin-for-gcp:v1.0.0
cs

설치가 진행 되면 관련 서버 및 resource 설치가 진행된다. backup-location 리소스 이름이 default인데 인증키 정보 파일의 인증 정보 탭이름을 일치시켜줘야 한다.

모든 설치 완료 후 버전 확인을 하면 클라이언트 버전과 서버 버전이 모두 보여진다. 제대로 설치가 되지 않으면 server 버전정보가 표시 되지 않는다.

1
2
3
4
5
6
$ velero version
Client : 
    Version: v1.1.0
    Git commit: <commit hash data>
Server:
    Version: v1.1.0     # minio와 연동 실패 등으로 서버가 제대로 구동되지 않을 수 있다.
cs

velero 서버 삭제는 deployment와 관련 리소스 삭제를 같이 진행해야 초기화 설치가 가능하다.

1
2
kubectl delete namespace/velero clusterrolebinding/velero  # 재설치의 경우 굳이 삭제하지 않아도 된다.
kubectl delete crds -l component=velero
cs




velero 간단 사용법


백업

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 네임스페이스 지정
$ velero backup create <BACKUP_NAME> --include-namespaces tpas,heimdall,kafka
 
 
# 특정 네임스페이스 제외
$ velero backup create <BACKUP_NAME> --exclude-namespaces default,kube-system
 
 
# 네임스페이스의 특정 리소스 제외
$ velero backup create <BACKUP_NAME> --include-namespaces tpas,heimdall,kafka --exclude-resources ingress,service
 
 
# 백업 데이터 확인 / 어떤 리소스가 백업되어 있는지 확인 가능
$ velero backup describe <BACKUP_NAME> --details
cs

기본적은 특정 네임스페이스에 포함된 리소스들에 대한 백업 데이터를 생성하는 명령어이다.

생성이 되면 해당 리소스들에 대한 정보가 json화 되어 연동된 minio에 생성한 bucket에 저장된다.

bucket 저장된 데이터는 압축된 데이터로 따로 다운받아서 확인 가능하며, describe 명령어로 백업 리소스 리스트를 확인 가능하다.



복원

1
velero restore create <restore-name> --from-backup <backup-name>
cs

백업 데이터로 부터 복원해주는 명령어로 minio에는 복원 결과와 로그가 남는다.



스케줄링 백업

1
velero schedule create <schedule-name> --schedule="0 1 * * * or @daily" --include-resources deployments,service
cs

스케줄링 백업 데이터를 생성하는 명령어로 crontab 또는 지정된 명령어를 지정하여 주기적인 백업 데이터를 생성할 수 있다.

1
2
3
4
5
6
7
Entry                  | Description                                | Equivalent To
-----                  | -----------                                | -------------
@yearly (or @annually) | Run once a year, midnight, Jan. 1st        | 0 0 1 1 *
@monthly               | Run once a month, midnight, first of month | 0 0 1 * *
@weekly                | Run once a week, midnight between Sat/Sun  | 0 0 * * 0
@daily (or @midnight)  | Run once a day, midnight                   | 0 0 * * *
@hourly                | Run once an hour, beginning of hour        | 0 * * * *
cs



백업 삭제

1
velero backup delete <backup-name>
cs

생성한 백업 데이터를 삭제한다.


운영 환경에서는 필요하다면 사용할 수 있겠지만 보통 운영환경에서 잦은 배포가 발생한다해도 환경변수는 고정인 경우가 많아서 아직 편의성을 제공하는 정도로 와닫지 않는 것 같다.
기술은 활용법의 문제이므로 상황에 맞게 사용법을 고민하면 될 것 같다.




kubernetes Admission Controller 에서 Webhook 사용한 Pod 배포 - 2 docker / kubernetes


지난 포스트에서 Webhook 서버 구축을 위한 기본 개념을 정리해보았다.


이번 포스팅에선 실제 구축 방법에 대해 정리하겠다.




Webhook 서버

동적으로 컨테이너를 injection 하기 위한 webhook 서버를 진행해보겠다.

먼저 Https 통신을 위한 SSL 인증서를 생성한다. 

본인은 kubernetes가 구축된 리눅스 서버에 생성했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ openssl req -nodes -new -x509 -keyout ca.key -out ca.crt -subj "/CN=CoDI prototype CA"
$ openssl genrsa -out webhook-server-tls.key 2048
$ openssl req -new -key webhook-server-tls.key -subj "/CN=codi-server.codi.svc" \
    | openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -out webhook-server-tls.crt
 
$ kubectl -n codi create codi-server tls webhook-certs \
     --cert "webhook-server-tls.crt" \
     --key "webhook-server-tls.key"
 
// 출력 값은 복사해서 webhookconfiguration 생성 시에 넣어준다.
echo $(openssl base64 -< "ca.crt"
 
$ openssl pkcs12 -export -in ca.crt -inkey ca.key -out codi-server.p12 -name codi-server.codi.svc
cs

인증서 생성 시 CN 값으로 webhookconfiguration에서 사용될 service 내부 도메인 명으로 생성해야 한다.

service 내부 도메인명과 다를 경우 인증에러가 발생한다. 인증서 ca를 base64 인코딩된 출력한 값은 webhookconfiguration 생성 시에 필요하므로 잘 저장해두자.

spring boot 경우 ssl 인증서에 PKCS12 / JKS 를 지원하는데 여기 사용하기 위한 p12 인증서도 생성한다.

인증서가 생성됐다면 webhook 요청을 받아 처리할 서버를 구축한다.

기본 spring boot 프로젝트 세팅 후 SSL 세팅을 진행한다. 프로젝트 세팅 과정은 생략.

1
2
3
4
5
6
7
8
9
10
config: 
  http.port: 8080
  path: /home/codi/config/config
server:
  port: 8443
  ssl: 
    key-store: file:/home/codi/config/codi-server.p12
    key-store-type: PKCS12
    key-store-password: 123456
    key-alias: codi-server.codi.svc
cs

path 경로는 kubernetes config 파일 경로이다. kubernetes api 서버와 통신하기 위해 필요하다.

ssl 인증서는 위에 생성한 인증서의 경로이다.

서로 각각 생성하여 사용하면 인증에러가 발생하므로 확인하고 진행하기 바란다.


또한 spring boot는 SSL 세팅이 되면 기본 http 통신은 사용할 수 없게 된다. 

그래서 동시 오픈을 위해 따로 설정이 필요하다.

1
2
3
4
5
6
7
8
9
10
11
12
@Value("${config.http.port}"
private int httpPort;
 
@Bean
public ServletWebServerFactory servletContainer() {
    Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
    connector.setPort(httpPort);
 
    TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
    tomcat.addAdditionalTomcatConnectors(connector);
    return tomcat;
}
cs


설정이 끝났으면 jsonpatch 데이터를 생성 하기 위한 dependency를 추가한다.

1
2
3
4
5
6
7
8
9
10
11
// gradle
dependency { 
    compile 'com.github.fge:json-patch:1.9'
}
 
// maven
<dependency>
  <groupId>com.github.fge</groupId>
  <artifactId>json-patch</artifactId>
  <version>1.9</version>
</dependency>
cs


여기까지 기본 프로젝트 세팅이 완료됐다. 

이제 webhook 서버로 요청을 받기 위한 api를 작성한다.

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@Value("${config.path}")
private String configPath;
 
@PostMapping(value = "/pods", produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public String podList(HttpServletRequest request) {
    StringBuffer sb = new StringBuffer();
    String reviewString = null;
    Map<String, Map<String, Ojbect>> reviewMap = new HashMap<>();
    Map<String, Ojbect> admissionResponse = new HashMap<>();
    Map<String, Ojbect> responseMap = new HashMap<>();
  
    Gson gson = new Gson();
  
    try {
        // POST 요청이기 때문에 request 데이터 파싱 위한 read
        request.getReader().lines().forEach(review -> {
        sb.append(review);
    });
 
    reviewString = sb.toString();
    reviewMap = gson.fromJson(reviewString, HashMap.class);
 
    // 변경 사항 비교 위한 origin 객체와 수정될 객체를 각각 저장
    V1Pod sourceObjectPod = gson.fromJson(gson.toJson(reviewMap.get("request").get("object")), V1Pod.class);
    V1Pod targetObjectPod = gson.fromJson(gson.toJson(reviewMap.get("request").get("object")), V1Pod.class);
  
    responseMap.put("allowed"true);
    responseMap.put("uid", reviewMap.get("request").get("uid").toString());
   
    if (sourceObjectPod.getMetadata().getAnnotations().get("com.codi-server/test"!= null) {
        List<V1Container> reviewContainer = targetObjectPod.getSpec().getContainers();
 
        List<V1Container> initList = new ArrayList<>();
        V1Container initContainer = new V1Container();
        initContainer.setImage("busybox");
        initContainer.setName("sleeping");
        initContainer.setCommand(Arrays.asList("sleep""10"));
        initList.add(initContainer);
        // initContainer 정보 저장
        targetObjectPod.getSpec().setInitContainers(initList);
     
        V1Container container = new V1Container();
        container.setImage("rancher/nginx:1.15.8-alpine");
        container.setName("webserver");
        reviewContainer.add(container);
        // 기존 pod내의 컨테이너 추가
        targetObjectPod.getSpec().setContainers(reviewContainer);
     
        ObjectMapper mapper = new ObjectMapper();
    
        JsonNode sourceNode = mapper.readTree(gson.toJson(sourceObjectPod)); 
        JsonNode targetNode = mapper.readTree(gson.toJson(targetObjectPod));
 
        // 달라진 데이터에 대한 비교 정보 jsonPatch 생성
        JsonNode diffJson = JsonDiff.asJson(sourceNode, targetNode);
     
        responseMap.put("patchType""JSONPatch");
        responseMap.put("patch", Base64Utils.encodeToString(diffJson.toString().getBytes()));
    }
   
    admissionResponse.put("response", responseMap);
 
    } catch (Exception e) {
      log.error("", e);
    }
  
    return gson.toJson(admissionResponse);
}
cs

소스를 보면 jsonPatch 생성 부분에서 JsonNode를 생성하는 것을 확인 할 수 있다.

다음이 jsonpatch 포맷이다.

1
2
3
4
5
[
  { "op""replace""path""/baz""value""boo" },
  { "op""add""path""/hello""value": ["world"] },
  { "op""remove""path""/foo" }
]
cs

하지만 jsonpatch 객체로 생성하면 다음과 같이 포맷이 생성된다.

1
["op""replace""path""/baz""value""boo"]
cs

그래서 데이터를 리턴해도 변경사항이 반영이 안되게 된다. 

여러 시도 끝에 그냥 JsonNode 객체로 Json 데이터를 생성하니 원하는 포맷으로 출력이 되었다.

이제 Webhook 서버 구현까지 완료 됐다면 kubernetes에 배포만 하면 된다.



MutatingWebhookConfiguration 생성 및 Pod/Service 생성
 
이번 포스팅에서는 생성되는 Pod에 대해 컨테이너를 사이드카로 배포 하므로 MutatingAdmission과 통신하는 WebhookConfiguration을 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: webhook-server
  namespace: codi
webhooks:
- name: codi-server.codi.svc     // {servive 명}.{namespace 명}.{endpoint 명}
  rules:
  - operations: ["CREATE"]
    apiGroups: ["*"]
    apiVersions: ["v1""v1beta1"]
    resources: ["pods"]
  clientConfig:
    caBundle: "Ci0tLS0tQk......tLS0K"
    service:
      namespace: codi
      name: codi-server
      path: /pods
cs

caBundle 에는 인증서 생성 시 출력한 base64 인코딩된 ca.crt 값을 넣어준다.

위에도 설명 했듯이 webhook 이름은 서비스 내부 도메인명을 맞춰줘야 한다.

rules에 정의했듯이 resources에 대해 create 시에 webhook 요청이 정의한 service를 통해 들어오게 된다.

생성이 됐다면 deployment와 service를 정의하여 webhook 서버를 배포한다.

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
apiVersion: apps/v1
kind: Deployment
metadata:
  name: codi-deployment
  namespace: codi
  labels:
    app: codi
spec:
  replicas: 1
  selector:
    matchLabels:
      app: codi
  template:
    metadata:
      labels:
        app: codi
      annotation:
        com.codi-server/test: enabled
    spec:
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: codi-container
        image: codi/codi:1.0.0
        imagePullPolicy: "Always"
        ports:
        - name: tomcat-https
          containerPort: 8443
        resources:
           requests:
             memory: "1Gi"
             cpu: "1200m"
           limits:
             memory: "2Gi"
             cpu: "2400m"
        volumeMounts:
        - name: logs
          mountPath: /home/codi
        - name: kube-config
          mountPath: /home/codi/config
        - name: resource
          mountPath: /home/codi/resource
      volumes:
      - name: logs
        hostPath:
          path: /home/platform/data/codi
          type: Directory
      - name: kube-config
        hostPath:
          path: /root/.kube
          type: Directory
      - name: resource
        hostPath:
          path: /home/platform/data/codi/resource
          type: Directory
 
---
 
apiVersion: v1
kind: Service
metadata:
  name: codi-server
  namespace: codi
spec:
  selector:
    app: codi
  ports:
    - port: 443
      name: webhook-https
cs

webhook 서버가 배포된 상태에서 metadata의 annotation에 'com.codi-server/test' 값을 세팅한 pod가 배포되면 api에 작성한 대로 추가적인 컨테이너가 배포된다.


마무리

여기까지 kubernetes webhook을 이용한 Pod내에 컨테이너 배포를 동적으로 할 수 있도록 하는 내용을 포스팅해봤다. 실제 구현은 어렵지 않지만 중간중간 지켜야 될 부분들이 있기 때문에 구현 시에 체크하가며 진행해야한다.

검색하면 python 예제들은 있는데 Java 예제가 없어서 정리한 포스팅이다. 참고하시는 분들에게 도움이 되길 바란다.




kubernetes Admission Controller 에서 Webhook 사용한 Pod 배포 - 1 docker / kubernetes


Pod 구성/배포

pod 배포 시 하나의 컨테이너 혹은 여러 개의 컨테이너를 구성해서 배포한다.
매번 yaml 파일에 특정 컨테이너 정보를 입력하는 반복작업이 발생하는데 특정 메타데이터 등을 체크하여 필요한 기능을 하는 어플리케이션을 사이드 컨테이너로 배포해주면 편리해진다.

이런 식의 pod 구성 혹은 배포 패턴이 존재하며, 하나의 pod 내에 여러 container를 배포하는 패턴을 sidecar / ambassador(proxy) / adapter 패턴이라 한다.

kubernetes에는 컨테이너 배포 시에 pod를 체크하고 관리, 실행 해주는 플러그인이 존재하는데 Admission Controller 라고한다.
이 플러그인에는 여러 phase가 존재하며, 여기서 Mutating/Validating Admission phase에 대해 webhook server를 구현해서 커스터마이징 플러그인으로 사용 가능하다.



Admission Controller

클러스터 사용 방식을 관리하고 실행하는 플러그인으로 Kubernetes API 요청을 인터셉트하고 요청 오브젝트를 변경하거나 거부 할 수 있는 게이트키퍼 역할이다.

다음 그림은 kubernetes 공식 블로그 가이드 포스팅의 그림이다.

Admission Controller Phases
Admission Controller Phases

우리가 webhook을 구현하여 통신하는 phase가 Mutating admission과 Validating admission으로 각각 오브젝트의 상태 변이와 유효성 검사 단계이다.

admission controller를 통해서
  • resource request, limit을 자동으로 설정
  • sidecar container injection 처리
  • namespace 내에서의 새로운 object 생성 및 제한
등등이 이루어진다. 더 자세한 내용은 공식블로그 포스팅을 참고바란다.

Webhook Server는 기본적으로 https 통신을 하기 때문에 먼저 ssl 인증서가 필요하다.
Service를 만들어서 443포트를 직접 구현한 webhook-server와 연결해 준 뒤 각 단계에 맞는 MutatingWebhookConfigurationValidatingWebhookConfiguration을 생성한다.
둘 다 구성 할 필요는 없으며 필요한 단계에 맞는 phase를 연동하면 된다.



WebHook

Webhook 서버를 구현하면 create, update, delete 등의 동작마다 AdmissionReview 객체가 넘어온다.
이 데이터를 파싱해서 우리가 원하는 작업을 하면 된다.

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
27
28
{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "request": {
     "uid": "705ab4f5-6393-11e8-b7cc-42010a800002",
     "kind": {"group":"autoscaling","version":"v1","kind":"Scale"},
     "resource": {"group":"apps","version":"v1","resource":"deployments"},
     "subResource": "scale",
     "requestKind": {"group":"autoscaling","version":"v1","kind":"Scale"},
     "requestResource": {"group":"apps","version":"v1","resource":"deployments"},
     "requestSubResource": "scale",
     "name": "my-deployment",
     "namespace": "my-namespace",
     "operation": "UPDATE",
     "userInfo": {
       "username": "admin",
       "uid": "014fbff9a07c",
       "groups": ["system:authenticated","my-admin-group"],
       "extra": {
         "some-key":["some-value1", "some-value2"]
       }
     },
     "object": {"apiVersion":"autoscaling/v1","kind":"Scale",...},
     "oldObject": {"apiVersion":"autoscaling/v1","kind":"Scale",...},
     "options": {"apiVersion":"meta.k8s.io/v1","kind":"UpdateOptions",...},
     "dryRun": false
  }
}
cs


공식 홈페이지에 나와 있는 json 데이터로 object 에 담겨 있는 데이터가 우리가 가공할 데이터이다.
데이터 처리가 끝나면 response를 넘겨주는데 jsonpatch를 사용해야 된다.

1
2
3
4
5
6
"response": {
    "uid": "{value from request.uid}",
    "allowed": true,
    "patchType": "JSONPatch",
    "patch": "W3sib3AiOiAiYWRkIiwgInBhdGgiOiAiL3NwZWMvcmVwbGljYXMiLCAidmFsdWUiOiAzfV0="
}
cs

변경할 데이터가 없을 때는 patchType, patch는 생략하면 된다. patch 값은 보이듯이 base64 인코딩 후 String 값을 넣어주면 된다. 또한 요청에 대해 거절할 때는 allowed 값 false, 상태코드, 메시지 등을 첨부 해줄 수 있다.
1
2
3
4
5
6
7
8
"response": {
  "uid": "{value from request.uid}",
  "allowed": false,
  "status": {
    "code": 403,
    "message": "You cannot do this because it is Tuesday and your name starts with A"
  }
}
cs


여기까지 Webhook 서버를 구현하기 위한 기본 내용들로 보이는 내용에 비해 어려운 구현은 아니다.

다음 포스팅에서 실제 구현하는 내용들을 정리해보도록 하겠다.





1 2 3 4 5 6 7 8 9 10 다음



광고