반응형

Pod 가 새롭게 생성되면 Scheduler 를 통해서 Pod 가 실행될 노드가 결정된다. 즉 모든 Pod 는 Scheduler 에 의해서 최적의 노드가 결정되고 실행된다.

 

kube-scheduler 는 Control Plane 에 속하는 컴포넌트 중에 하나이며, 디폴트 scheduler 이다. 만약 특별한 스케줄링을 원한다며 커스텀 스케줄러를 만들어서 대체할 수 도 있다.

 

Pod 가 스케줄링 되기 위해서는 노드가 스케줄링 요구 사항에 맞아야 하는데 이 때, 요구사항에 맞는 노드를 피저블 노드(feasible node) 라고 한다.

 

스케줄러는 피저블 노드를 찾고 적절한 로직을 통해 노드에 점수를 부여한다. 가장 최고 점수를 갖는 노드에 Pod 가 실행된다.

스케줄링을 결정하는 데에는 하드웨어, 소프트웨어, policy contraints, affinity 와 anti-affinity spec, local data 등 여러 요구 사항들이 있다.

 

kube-scheduler 가 결정하는 2-step operation

  1. Filtering
  2. Scoring

Filtering 단계에서는 피저블 노드를 찾는 단계이다. 예를 들면, PodFitsResources 필터는 Pod 의 request 리소스를 만족하는 리소스를 갖는 후보 노드를 추려낸다. 이 단계 이후에는 적합한 노드 리스트가 도출되는데 만약 노드 리스트의 값이 비었다면 Pod 는 스케줄링 되지 않는다.

Scoring 단계에서는 필터링된 노드 리스트 중에서 Pod 가 실행될 최적의 노드를 찾는다. 스케줄러는 노드 리스트에 있는 각 노드들에 점수를 부여한다.

마지막으로 kube-scheduler 는 최고 점수의 노드에 Pod 를 배치한다.

Filtering 과 Scoring 을 설정할 수 있는 방법은 2가지가 있다.

  1. Scheduling Policies (kubernetes 1.22 버전까지만 사용)
    • Predicates : filtering 을 조절할 수 있음
    • Priorities: scoring 을 조절할 수 있음
  2. Scheduling Profiles (https://kubernetes.io/docs/reference/scheduling/config/#profiles)
    • KubeSchedulerConfiguration 리소스 타입으로 조절할 수 있음
    • plugins 를 통해 Filter 와 Score 를 조절 가능

 

nodeSelector

Node 에 label 을 설정하고 Pod 에서 nodeSelector 로 Node 지정하는 방식이다.

$ kubectl get nodes -L nginx
NAME          STATUS   ROLES                  AGE   VERSION   NGINX
k1-master01   Ready    control-plane,master   92d   v1.21.6
k1-node01     Ready    ingress                92d   v1.21.6
k1-node02     Ready    <none>                 92d   v1.21.6   enabled
k1-node03     Ready    <none>                 92d   v1.21.6   enabled
k1-node04     Ready    <none>                 92d   v1.21.6
k1-node05     Ready    <none>                 92d   v1.21.6

k1-node02 와 k1-node03 에 nginx=enabled 로 label 이 지정되어 있다.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-nodeselector
  namespace: kube-sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.21.0
        ports:
        - containerPort: 80
      nodeSelector:
        nginx: enabled

nodeSelector 를 활용하여 deployment 를 배포하면 아래와 같이 k1-node02 혹은 k1-node03 에 배포된다.

 

$ kubectl get pods -n kube-sample -o wide
NAME                                  READY   STATUS    RESTARTS   AGE   IP             NODE        NOMINATED NODE   READINESS GATES
nginx-nodeselector-68977d7759-sf48l   1/1     Running   0          9s    10.233.113.7   k1-node02   <none>           <none>

 

Node affinity

nodeSelector 와 동작 방식이 유사하지만 다음과 같은 차이가 있다.

requiredDuringSchedulingIgnoredDuringExecution: 스케줄링에서 만족하는 노드가 없으면 스케줄링 되지 않는다.

preferredDuringSchedulingIgnoredDuringExecution: 스케줄링에서 만족하는 노드를 찾고 노드가 없더라도 스케줄링은 된다. weight 필드의 값을 scoring 에 포함시킨다.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-node-affinity
  namespace: kube-sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: nginx
                operator: In
                values:
                - enabled
            - matchExpressions:
              - key: kubernetes.io/os
                operator: In
                values:
                - windows
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 1
            preference:
              matchExpressions:
              - key: another-node-label-key
                operator: In
                values:
                - another-node-label-value
      containers:
      - name: nginx
        image: nginx:1.21.0
        ports:
        - containerPort: 80

위와 같이 nodeSelectorTerms 에 여러 개의 matchExpressions 가 있으면 이 중 하나만 만족해도 스케줄링은 된다. nodeSelectorTerms 에 하나의 matchExpressions 가 있고, key value 가 여러 개가 있으면 matchExpressions 내의 모든 조건을 만족해야 스케줄링이 된다.

operator 로 쓸 수 있는 값은 In, NotIn, Exists, DoesNotExist, Gt, Lt 이다.

NotInDoesNotExist 는 node anti-affinity 로 사용 가능하다.

 

$ kubectl get pods -n kube-sample -o wide
NAME                                   READY   STATUS        RESTARTS   AGE    IP              NODE        NOMINATED NODE   READINESS GATES
nginx-node-affinity-755cf7f85d-ss9q5   1/1     Running       0          5s     10.233.113.10   k1-node02   <none>           <none>

 

Pod affinity and anti-affinity

Pod 를 서로 다른 노드에 배치시키는 방법은 pod anti-affinity 를 통해서 구현할 수 있다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-pod-anti-affinity
  namespace: kube-sample
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-pod-anti-affinity
  template:
    metadata:
      labels:
        app: nginx-pod-anti-affinity
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - nginx-pod-anti-affinity
            topologyKey: kubernetes.io/hostname
      containers:
      - name: nginx
        image: nginx:1.21.0
        ports:
        - containerPort: 80

위와 같이 pod label 에 pod anti affinity 를 설정하면 replicas 값 2 에 의해서 생성되는 2개의 pod 는 서로 다른 노드에 배치된다. topologyKey 는 kubernetes.io/hostname 값으로 세팅하고 특정 node lable 로 하고 싶으면 node affinity 를 추가하면 된다.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-pod-anti-affinity
  namespace: kube-sample
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-pod-anti-affinity
  template:
    metadata:
      labels:
        app: nginx-pod-anti-affinity
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - nginx-pod-anti-affinity
            topologyKey: kubernetes.io/hostname
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: nginx
                operator: In
                values:
                - enabled
      containers:
      - name: nginx
        image: nginx:1.21.0
        ports:
        - containerPort: 80
$ kubectl get pods -n kube-sample -o wide
NAME                                       READY   STATUS    RESTARTS   AGE   IP              NODE        NOMINATED NODE   READINESS GATES
nginx-pod-anti-affinity-7cfcbcff8f-c2429   1/1     Running   0          14s   10.233.96.3     k1-node03   <none>           <none>
nginx-pod-anti-affinity-7cfcbcff8f-dmqqm   1/1     Running   0          14s   10.233.113.12   k1-node02   <none>           <none>

 

Taint 와 Toleration

노드에 taint 를 지정하면 해당 노드에 taint effect 를 줄 수 있다. 예를 들어 taint effect 에 NoSchedule 을 준다면 해당 노드에는 pod 가 스케줄링 되지 않는다.

taint effect 를 제거할 수 있는 방법은 Pod 에 toleration 을 세팅하는 것이다.

마스터에는 기본적으로 taint 를 지정해서 스케줄링 안되게 세팅해 놓는 것을 알 수 있다.

 

$ kubectl describe node k1-master01

Name:               k1-master01
Roles:              control-plane,master
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=k1-master01
                    kubernetes.io/os=linux
                    node-role.kubernetes.io/control-plane=
                    node-role.kubernetes.io/master=
                    node.kubernetes.io/exclude-from-external-load-balancers=
Annotations:        kubeadm.alpha.kubernetes.io/cri-socket: /var/run/containerd/containerd.sock
                    node.alpha.kubernetes.io/ttl: 0
                    projectcalico.org/IPv4Address: 192.168.30.13/24
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Tue, 08 Feb 2022 21:27:12 +0900
Taints:             node-role.kubernetes.io/master:NoSchedule

마지막의 Taints 값은 kubectl 로 세팅할 수 있다.

 

$ kubectl taint nodes k1-master01 node-role.kubernetes.io/master=:NoSchedule

그럼 master 노드에 toleration 을 활용하여 pod 를 배치해 보자.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-toleration
  namespace: kube-sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-toleration
  template:
    metadata:
      labels:
        app: nginx-toleration
    spec:
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: node-role.kubernetes.io/control-plane
                operator: Exists
      containers:
      - name: nginx
        image: nginx:1.21.0
        ports:
        - containerPort: 80

toleration 으로 taint 효과를 없애고 node affinity 를 활용하여 control-plane, 즉 master 노드에 pod 를 배치 시켰다. 물론 master 노드에 node-role.kubernetes.io/control-plane 이라는 label 이 존재해야 한다.

 

taint effect 는 NoSchedule, NoExecute, PreferNoSchedule 이 있다.

 

NoSchedule 은 스케줄링을 막는 것이고, NoExecute 는 실행중인 pod 를 eviction 시킨다. PreferNoSchedule 은 가능하면 스케줄링 하지 않는다는 의미이다.

 

operator 에는 Exists 와 Equal 이 있다. Exists 는 key 값이 존재하는 지를 판단하기 때문에 value 값이 필요 없으며, Equal 은 key 와 value 값이 지정된 값과 일치해야 한다.

 

$ kubectl get pods -n kube-sample -o wide
NAME                                       READY   STATUS    RESTARTS   AGE     IP              NODE          NOMINATED NODE   READINESS GATES
nginx-toleration-fb74cd98c-hkql8           1/1     Running   0          5m31s   10.233.117.4    k1-master01   <none>           <none>

 

반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

EKS 서비스를 이용하기 보다는 Kubernetes 를 AWS 에 직접 설치하여 활용하는 경우가 종종 있는데, 이 경우에는 세부적인 세팅들을 해줘야 할 때가 있다. 그 중에 대표적인 추가 모듈이 Pod 에서 EBS Volume 을 연결하여 사용할 수 있는 StorageClass 이다.

 

잘못된 Storage Class 를 사용하는 경우

Storage class 의 Provisioner 를 kubernetes.io/aws-ebs 로 사용하는 경우가 있는데, 이는 처음에는 문제가 없으나 Pod eviction (pod 가 다른 노드로 옮겨지는 경우) 에서는 문제가 발생한다. 

아래와 같은 storage class 를 적용한다.

---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: bad-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
  fsType: ext4

 

처음 만든 Pod 에서 위의 StorageClass 를 사용하여 volume 을 ReadWriteOnce 로 attach 한 경우에는 문제없이 정상 작동한다. 하지만 pod 가 rescheduling 되면 새로 옮겨가는 노드에서 pod 는 생성되지 않는다. 이유는 volume 이 이전 노드의 Pod에 이미 사용되고 있다는 정보 때문에 새로운 노드의 Pod 에서 volume attach 가 안되기 때문이다.

즉, 해당 문제를 해결하기 위해서는 이전의 attach 정보를 삭제해 줘야 하는데 위의 storage class 로는 이를 해결할 수 없다. 그래서 다음과 같은 StroageClass 를 사용해야 한다.

 

올바른 Storage Class 사용하기

1. CSI Provisioner 설치

AWS EBS 를 위한 CSI Driver 는 다음과 같이 설치해야 한다.

$ kubectl apply -k "github.com/kubernetes-sigs/aws-ebs-csi-driver/deploy/kubernetes/overlays/stable/?ref=release-1.5"


$ kubectl get pods -n kube-system
...
ebs-csi-controller-6fd55f99c8-9j2hr                                       6/6     Running   0             13d
ebs-csi-controller-6fd55f99c8-x5dkt                                       6/6     Running   0             13d
ebs-csi-node-bw6jb                                                        3/3     Running   0             13d
ebs-csi-node-nx7gl                                                        3/3     Running   0             13d
ebs-csi-node-zh242                                                        3/3     Running   0             13d
...

 

kustomize 로 설치하면 ebs-csi-controller 가 HA 로 설치되고, 각 노드 마다 ebs-csi-node 가 설치된다. 이는 ceph 스토리지 용도의 CSI Driver 와도 비슷한데 volume 을 사용할 Pod 가 실행될 VM 노드에 EBS 링크를 연결하고 이를 Pod 에서 사용하기 때문에 각 노드마다 ebs-csi-node 가 설치되는 이유이다.

 

2. Storage class 생성

driver 를 설치했으면 이제 스토리지 클래스를 생성할 차례이다. 아래와 같이 생성한다.

$ vi standard-ebs-sc.yaml
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  annotations: 
    storageclass.kubernetes.io/is-default-class: "true"
  name: standard
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  csi.storage.k8s.io/fstype: xfs
  type: io1
  iopsPerGB: "50"
  encrypted: "false"
reclaimPolicy: Delete
allowVolumeExpansion: true
allowedTopologies:
- matchLabelExpressions:
  - key: topology.ebs.csi.aws.com/zone
    values:
    - ap-northeast-2a
    - ap-northeast-2b
    - ap-northeast-2c



$ kubectl apply -f standard-ebs-sc.yaml

 

annotations 의 storageclass.kubernetes.io/is-default-class":"true" 값은 스토리지 클래스를 디폴트로 지정하는 값이다. 스토리지 클래스는 여러가지를 사용할 수 있기 때문에 기본을 지정해 주는 것이고, 또한 ebs 가 생성될 AZ 리스트를 넣어줘야 한다.

 

3. IAM 설정

앞에서 VM 에 ebs 링크를 걸어준다고 했는데 그렇기 때문에 IAM 설정을 해야 한다. 아래와 같이 Policy 를 만들고 해당 Policy 를  control-plane.cluster-api-provider-aws.sigs.k8s.io 과 nodes.cluster-api-provider-aws.sigs.k8s.io 에 attach 해야 한다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateSnapshot",
                "ec2:AttachVolume",
                "ec2:DetachVolume",
                "ec2:ModifyVolume",
                "ec2:DescribeAvailabilityZones",
                "ec2:DescribeInstances",
                "ec2:DescribeSnapshots",
                "ec2:DescribeTags",
                "ec2:DescribeVolumes",
                "ec2:DescribeVolumesModifications"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateTags"
            ],
            "Resource": [
                "arn:aws:ec2:*:*:volume/*",
                "arn:aws:ec2:*:*:snapshot/*"
            ],
            "Condition": {
                "StringEquals": {
                    "ec2:CreateAction": [
                        "CreateVolume",
                        "CreateSnapshot"
                    ]
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DeleteTags"
            ],
            "Resource": [
                "arn:aws:ec2:*:*:volume/*",
                "arn:aws:ec2:*:*:snapshot/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateVolume"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateVolume"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateVolume"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DeleteVolume"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DeleteVolume"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DeleteVolume"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DeleteSnapshot"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DeleteSnapshot"
            ],
            "Resource": "*"
        }
    ]
}

 

4. 테스트

샘플 코드를 만들어서 잘 동작하는지 테스트 해 보자.

$ vi app-ebs-example.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-ebs-claim
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: standard
  resources:
    requests:
      storage: 4Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: centos
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeCl
      claimName: app-ebs-claim



$ kubectl apply -f app-ebs-example.yaml

 

위의 코드를 설치하면 pvc 가 아래와 같이 잘 생성되는 것을 알 수 있다.

$ kubectl get pvc
NAME            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
app-ebs-claim   Bound    pvc-4ab38fe9-7e76-4d81-85d3-0db68da9c1e0   4Gi        RWO            standard       6s


$ kubectl get volumeattachments
NAME                                                                   ATTACHER          PV                                         NODE                                             ATTACHED   AGE
csi-60f252627e620b1e91d7d32a7d463b406a3850f3a7d7b0c535fe209f699ba3bd   ebs.csi.aws.com   pvc-4ab38fe9-7e76-4d81-85d3-0db68da9c1e0   ip-10-0-214-57.ap-northeast-2.compute.internal   true       2m8s

 

또한 volumeattachments 를 보면 VM 에 해당 volume 이 attach 되어 링크가 걸려 있는 것을 알 수 있다.

 

 

반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

배경

외부에서 Kubernetes Cluster 위에서 돌아가는 서비스에 접근하기 위해서는 Ingress Controller 를 통해서 가능하다. aws 와 같은 클라우드에서는 ELB -> Ingress Controller -> Workload 경로로 접근가능하다.

현재 사용하고 있는 방법은 CrossPlane 으로 AWS ELB (Classic Type) 를 자동으로 생성하여 Ingress Controller 에 연결하고 있다. 그렇기 때문에 Ingress Controller 를 배포할 때 Service Type을 특정 NodePort 를 지정/오픈하여 연결점을 알아야 한다.

예를 들면 아래와 같이 value 값을 override 해야 한다.

controller:
  replicaCount: 2
  service:
    externalTrafficPolicy: Local
    type: NodePort
    nodePorts:
      http: 32080
      https: 32443
      tcp:
        10254: 32081
  hostPort:
    enabled: true
tcp:
  10254: "10254:healthz"

 

CrossPlane 이 AWS ELB 에 대해서 Classic Type 만 지원하기 때문에 CrossPlane 을 제거하고 Network Type 으로 변경하는 것을 고민하였다. 참고로 현재의 방법은 Ingress Controller 를 실수로 삭제해도 ELB 는 그대로 남겨져 있어야 한다는 전제로 고려한 방법이다. DNS 가 ELB 와 연동되어야 하기 때문에 ELB 의 삭제 후 재생성은 DNS 전파의 시간을 필요로 하기 때문이다. 

하지만 AWS 에서 권장하는 NLB 로 넘어가는 것, NodePort 를 지정하지 않아도 된다는 것, 그리고 CrossPlane 으로 ELB를 관리하지 않아도 되기 때문에 운영 측면에서 더 낫다고 판단되어 방법을 찾아보게 되었다.

 

해결책

Kubernetes 프로젝트 아래 Ingress Controller 의 annotation 을 사용하면 NLB 를 자동으로 생성할 수 있다.

$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
$ helm repo update

$ helm search repo ingress-nginx/ingress-nginx

$ vi ingress-nginx-values.yaml
---
controller:
  replicaCount: 2
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app.kubernetes.io/name
              operator: In
              values:
              - ingress-nginx
          topologyKey: "kubernetes.io/hostname"
  nodeSelector:
    app.kubernetes.io/name: ingress-nginx
  service:
    annotations:
#      service.beta.kubernetes.io/aws-load-balancer-name: "ahnsk-ingress"
      service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
      service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
#      service.beta.kubernetes.io/aws-load-balancer-target-group-attributes: "proxy_protocol_v2.enabled=true,preserve_client_ip.enabled=true,deregistration_delay.timeout_seconds=120,deregistration_delay.connection_termination.enabled=true"
    externalTrafficPolicy: Local
    type: LoadBalancer
#    healthCheckNodePort: 32081
  config:
    enable-underscores-in-headers: "true"
    use-proxy-protocol: "true"
    proxy-body-size: "10m"

 

nodeSelector 와 podAntiAffinity 를 사용하여 가능하면 지정노드에 분포해서 Ingress Controller 를 설치할 수 있다. 

Service 의 annotations 을 사용하면 ELB 를 NLB type 으로 생성하여 자동으로 Ingress Controller 에 연결시켜 준다. 한가지 주의할 점은 aws-load-balancer-proxy-protocol 을 사용하면 aws-load-balancer-target-group-attributes 의 proxy_protocol_v2 를 enabled 해야 한다. 그러나 이 annotations 은 동작하지 않는다. 왜냐하면 aws 에서는 Ingress Controller (https://github.com/kubernetes-sigs/aws-load-balancer-controller/tree/main/helm/aws-load-balancer-controller)를 kubernetes-sigs 아래에 따로 만들어서 관리하기 때문이다. 즉 더 이상의 Nginx Ingress Controller 에 대한 기능 추가가 없다고 공표했다.

그럼에도 불구하고 Nginx Ingress Controller 를 선호한다. 이유는 Nginx 의 기능, 즉 config 를 원하는 대로 세팅할 수 있기 때문이다. 

nginx 를 사용할 때 가장 많이 경험하는 에러가 2가지 있다. 하나는 header 에 '_' 값이 포함될 때 에러가 나고 다른 하나는 body-size 로 인한 에러이다. 이 2가지 값은 기본적으로 허용해 주는 것이 좋다. 

마지막으로 ELB, Ingress Controller 를 거치면서 real client ip 를 알고 싶어 하는 경우가 많다. nginx 의 use-proxy-protocol: "true" 로 client ip 를 알아 낼 수 있다.

 

이렇게 세팅한 값으로 Ingress Controller 를 배포한다.

$ kubectl label node ip-10-0-181-43.ap-northeast-2.compute.internal app.kubernetes.io/name=ingress-nginx
$ kubectl label node ip-10-0-214-57.ap-northeast-2.compute.internal app.kubernetes.io/name=ingress-nginx

$ helm upgrade -i ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx --version 4.0.16 --create-namespace -f ingress-nginx-values.yaml

 

배포가 진행될 때 다음과 같이 NLB 의 Target Group 속성 중에 proxy_protocol_v2 값을 활성화 해줘야 한다. aws console 에서도 가능하지만 aws cli 를 통해서도 가능하다.

$ aws elbv2 describe-target-groups | jq .'TargetGroups[] | select(.VpcId == "vpc id 값") | .TargetGroupArn'

--- output ---
"arn:aws:elasticloadbalancing:ap-northeast-2:..."
"arn:aws:elasticloadbalancing:ap-northeast-2:..."

$ aws elbv2 modify-target-group-attributes --target-group-arn arn:aws:elasticloadbalancing:ap-northeast-2:... --attributes 'Key=proxy_protocol_v2.enabled,Value=true'

$ aws elbv2 modify-target-group-attributes --target-group-arn arn:aws:elasticloadbalancing:ap-northeast-2:... --attributes 'Key=proxy_protocol_v2.enabled,Value=true'

 

테스트

sample application 을 배포하여 실제 호출이 잘 되는지 확인한다. 아래는 배포 후에 http header 를 출력해 본 결과이다.

➜ ~ curl -L http://nginx-ahnsk.taco-cat.xyz
GET / HTTP/1.1
Host: nginx-ahnsk.taco-cat.xyz
X-Request-ID: 1ca37cbd4fe84f1c20e56e7ce014bd4c
X-Real-IP: 218.237.0.56
X-Forwarded-For: 218.237.0.56
X-Forwarded-Host: nginx-ahnsk.taco-cat.xyz
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Forwarded-Scheme: https
X-Scheme: https
user-agent: curl/7.64.1
accept: */*

 

X-Real-IP 와 X-Forwared-For 에 실제 client ip 값이 출력되는 것을 확인할 수 있다.

 

마치며

Kubernetes 위에 서비스를 올린 후 사용자 접근을 오픈하기 위해서는 LB 와 Ingress Controller 를 사용해야 한다. 직접 Kubernetes 를 설치 관리하면서 Nginx 의 기능을 사용하고 싶다면 AWS Load Balancer Controller 보다는 Nginx Ingress Controller 를 활용해야 한다. 물론 IAM Account 연동 등을 위해서 혹은 EKS Cluster 를 사용한다면 AWS Load Balancer Controller 를 사용하는 것이 건강에 좋을 것이다.



반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

  1. odark 2022.06.14 16:07  댓글주소  수정/삭제  댓글쓰기

    nginx-ingress-controller를 설치하면 외부에서 서비스 접속하여 L7기반의 로드발랑싱 지원을 받고 내부 서비스를 받을수있는것이고 이건곧 ingress를 별도 설치,설정하여 가능한걸로 알고있습니다.
    annotation 으로 nlb 타입으로 선언해주면 NLB type 으로 생성하여 자동으로 Ingress Controller 에 연결시켜 준다고 하셨는데 이부분에서 헷갈리는데 ingress controller는 ingress 와 연동하여 L7 기반의 로드발랑싱이 가능한데 NLB타입의 elb가 생성되면 http/https 가 아닌 TCP 통신타입일것이고 그러면 ingress 룰과 연동이 의미가 없고 ingress 설치(?)도 필요없는거 아닌가요? 다시말하면 nginx-ingress-controller는 service생성이 어노테이션으로 nlb타입으로 선언하면 nlb타입의 elb를 생성해주는것으로 끝나는거 아닌가요?
    그러면 그 nlb 타입의 service가 생성되고 서비스가 selector에 의해 pod들을호출하는거 아닌지요?
    Ingress Controller 에 연결될 의미도 없는거잖아요... Ingress Controller 연결되는건 곧 ingress 룰을 타기위함일테구요...초보라 질문드려봅니다.

  2. BlogIcon seungkyua@gmail.com 2022.06.21 12:53 신고  댓글주소  수정/삭제  댓글쓰기

    nlb -> ingress controller -> pod 로 패스가 생깁니다. 그러므로 ingress controller 에서 L7 의 역할을 할 수 있어요. ingress controller 는 multi pod 로 뜰 수 있기 때문에 앞단에 LB 가 필요하고요.

반응형

2018년에 Kubernetes 상에서 돌아가는 ML Platform 인 Kubeflow 를 처음 알게된 후로 관심을 갖고 재미삼아 하다가 2022년에는 업무와 연관되어 일을 해야하기 때문에 중점으로 살펴보고자 한다. (최근에는 ML Platform 보다는 AI Platform 이라고 말하는 사람들이 많은 듯)

 

Kubeflow 를 KubeCon 에서 만나다

Kubeflow 개발자들을 처음 만난 것은 2019년 KubeCon in Europe (Barcelona) 이다. 초기 Kubeflow 는 Goolge 과 Arrito 가 주축이 되어 개발하고 있었는데 마침 초기 버전이 나오면서 Kubeflow 홍보를 시작하던 때 였었다.

Kubeflow 의 주 개발자는 화면에 보이는 구글의 Senior S/W Engineer 인 Jeremy Lewi 다. 당시 저 발표는 간단한 설명후에 데모 형태로 발표가 되었는데 Kubernetes 위에 Kubeflow 를 설치한 상태에서 Jupyter Notebook 을 띄어 Model 을 개발하고, Fairing 으로 Model Training 후 Model Serving 하는 것을 시연으로 보여주었다.

 

영상은 아래를 클릭하면 볼 수 있다.

https://www.youtube.com/watch?v=-GYiatVNemY 

 

저녁에 Arrito 가 주최하는 Kubeflow 개발자 / 사용자와의 저녁 만남이 있었는데 거기서 Jeremy 와 이야기를 해봤을 때는 전형적인 내성적이고 약간은 고지식한 개발자라는 느낌을 받았던 기억이(물론 이건 주관적인 생각). 시간이 좀 지나니 혼자 따로 앉아 있던데. 

 

 

Kubeflow 를 왜 써야 하나?

AI Platform Developer 라면 아래의 그림은 많이 봐왔을 것이다. 정 가운데에 있는 ML Code 는 데이터 사이언티스트들이 모델을 개발하는 코드인데, 이런 개발을 도와주거나 실제 서비스로 구축하기 위해서는 코드 이외에도 많은 Tool / System 들이 필요하다는 의미를 보여준다.  

https://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems.pdf

 

또한 위의 동영상에서도 아래와 같이 설명하고 있다.

 

 

간단하게 살펴보는 Kubeflow Architecture

아키텍처에 대한 설명은 Kubeflow document 사이트를 참고하여 간단히 설명하고자 한다.

https://www.kubeflow.org/docs/started/architecture/

 

 

 

ML 개발 프로세스는 Experimental Phase (실험 단계) 와 Production Phase (운영 단계) 로 나눌 수 있으며 전체 워크플로우는 아래와 같다.

실험 단계

1. 문제를 인식하고 데이터를 수집/분석

2. ML 알고리즘을 선택하여 모델을 코딩

3. 데이터를 가지고 모델 트레이닝

4. 하이퍼 파라미터 튜닝

 

운영 단계

1. 데이터 변환

2. 모델 트레이닝

3. 온라인/배치 예측을 위한 모델 서빙

4. 모델 성능 측정 (결과를 가지고 다시 트레이닝하거나 튜닝)

 

 

단계에 적용되는 Kubeflow 컴포넌트는 아래와 같다. 

 

  • Fairing: 위성발사를 생각해보면 로켓 맨 상단에 중요 내용물인 위성을 보호하기 위해서 Fairing 으로 감싸고 있다. 모델을 안전하게 다른 시스템으로 전달하는 기능으로 python library 를 제공한다.
  • Pipelines: Argo workflow 기반으로 python library 를 제공하여 pipeline 을 구축할 수 있다. 예를 들면 데이터 전처리 등 반복되는 작업을 자동으로 수행할 수 있다. 
  • Katib: 하이퍼 파라미터 튜닝을 자동으로 해주는 기능이다. 아래 그림과 같이 하이퍼 파라미터를 바꾸면서 결과를 그래프와 표로 보여준다.
  • KFServing:  Kubeflow 에서 제공해주는 Model Serving 기능이다.

 

마치며

현재 Kubeflow 의 최신 버전은 v1.4 이다. 2019년 Arrito 개발자들과 만나서 논의했을 때 (당시에 v0.3 인걸로 기억한다) v1.0 으로 올리는 것이 의미있냐고 질문을 받은 적이 있다. 고객이 오픈소스의 안정성을 의심하기 때문에 기능이 조금 미흡하더라도 v1.0 으로 올리는 것이 좋겠다는 의견을 준적이 있었는데 어느덧 버전이 v1.4 까지 나왔다.(물론 그 때 이후 한참 지나서야 v1.0 이 나왔지만)

많은 기업에서 자체 AI Platform 을 만들어서 사용하는 것으로 알고 있는데, 제 개인적인 생각으로는 지금이라도 Kubeflow 로 바꾸는 것이 좋다는 생각이다. 그 이유는 이미 모두 알고 있을 것이다.

반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

Helm chart 의 Life cycle 을 이해하면 조금 더 고급스러운 chart 를 만들 수 있다. 예를 들면 이전 chart 의 애플리케이션에서 postgresql DB를 사용하기 때문에 사용자, 데이터베이스, 테이블을 생성해야 하는 경우를 생각해 보자. 차트가 배포될 때 애플리케이션이 실행되기 이전에 해당 작업들을 할 수 있다면 chart 배포 한 번으로 모든 배포를 끝낼 수 있으니 멋진 일이다.

Helm 에서는 chart 가 실행되기 이전에 혹은, 실행된 이후에 작업을 정의할 수 있도록 hook 기능을 제공한다. 이러한 hook 의 종류는 아래와 같다.

 

Annotation 값 설 명
pre-install Executes after templates are rendered, but before any resources are created in Kubernetes
post-install Executes after all resources are loaded into Kubernetes
pre-delete Executes on a deletion request before any resources are deleted from Kubernetes
post-delete Executes on a deletion request after all of the release's resources have been deleted
pre-upgrade Executes on an upgrade request after templates are rendered, but before any resources are updated
post-upgrade Executes on an upgrade request after all resources have been upgraded
pre-rollback Executes on a rollback request after templates are rendered, but before any resources are rolled back
post-rollback Executes on a rollback request after all resources have been modified
test Executes when the Helm test subcommand is invoked ( view test docs)

 

hook 을 정의하는 것은 Annotation 으로 하며, 앞에서 설명한 시나리오의 경우에는 pre-install hook 을 사용하는 것이 적절하기 때문에 "helm.sh/hook": pre-install 로 설정한다. 또한 이런 단발성 호출의 경우에는 Job 으로 설정하는 것이 좋다.

apiVersion: batch/v1
kind: Job
metadata:
...
  annotations:
    "helm.sh/hook": pre-install
    "helm.sh/hook-weight": "1"
    "helm.sh/hook-delete-policy": before-hook-creation

 

hook-weight 는 낮을 수록 우선순위가 높으며, hook-delete-policy 의 before-hook-creation 은 hook 으로 실행된 리소스를 다음 hook 이 실행될 때 까지 지우지 않고 남겨둔다는 의미이다. policy 는 아래와 같이 3가지가 있다.

Annotation Value Description
before-hook-creation Delete the previous resource before a new hook is launched (default)
hook-succeeded Delete the resource after the hook is successfully executed
hook-failed Delete the resource if the hook failed during execution

그럼 postgresql 을 설치하고 사용자, 데이터베이스, 테이블을 생성하는 pre-install hook 을 작성해 보자.

 

 

1. postgresql 설치

bitnami chart repo 에서 postgresql 을 다운받아 설치한다.

$ helm repo add bitnami https://charts.bitnami.com/bitnami
$ helm repo update
$ helm upgrade -i postgresql bitnami/postgresql --version 10.8.0 -n decapod-db --create-namespace \
--set postgresqlPassword=password \
--set persistence.enabled=true \
--set persistence.storageClass=rbd \
--set persistence.size=10Gi

chart 의 value 값을 필요한 부분만 override 하였다. db user 는 postgres 이며 db password는 password 로 설치했다. 테스트 환경에서는 외부 스토리지를 제공하고 있어 10Gi 로 설정하였다.

$ kubectl get pods -n decapod-db
NAME                      READY   STATUS    RESTARTS   AGE
postgresql-postgresql-0   1/1     Running   0          35h


$ kubectl get svc -n decapod-db
NAME                  TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
postgresql            ClusterIP   10.233.46.14   <none>        5432/TCP   35h
postgresql-headless   ClusterIP   None           <none>        5432/TCP   35h

 

2. pre-install hook 만들기

chart 가 설치될 때 한 번 실행되면 되므로 Job 타입으로 리소스를 작성한다. psql 명령을 사용할 수 있는 컨테이너 이미지를 활용하고 수행할 명령어들은 쉘 스크립트로 작성하였다.

{chart_source_home}/templates/pre-install-job.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "tks-contract.fullname" . }}
  namespace: {{ .Values.namespace }}
  labels:
    {{- include "tks-contract.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": pre-install
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": before-hook-creation
spec:
  template:
    metadata:
      name: {{ include "tks-contract.fullname" . }}
    spec:
      restartPolicy: Never
      containers:
      - name: pre-install-job
        image: "bitnami/postgresql:11.12.0-debian-10-r44"
        env:
        - name: DB_ADMIN_USER
          value: {{ .Values.db.adminUser }}
        - name: PGPASSWORD
          value: {{ .Values.db.adminPassword }}
        - name: DB_NAME
          value: {{ .Values.db.dbName }}
        - name: DB_USER
          value: {{ .Values.args.dbUser }}
        - name: DB_PASSWORD
          value: {{ .Values.args.dbPassword }}
        - name: DB_URL
          value: {{ .Values.args.dbUrl }}
        - name: DB_PORT
          value: "{{ .Values.args.dbPort }}"
        command:
        - /bin/bash
        - -c
        - -x
        - |
          # check if ${DB_NAME} database already exists.
          psql -h ${DB_URL} -p ${DB_PORT} -U ${DB_ADMIN_USER} -lqt | cut -d \| -f 1 | grep -qw ${DB_NAME}
          if [[ $? -ne 0 ]]; then
            psql -h ${DB_URL} -p ${DB_PORT} -U ${DB_ADMIN_USER} -c "CREATE DATABASE ${DB_NAME};"
          fi

          # check if ${DB_USER} user already exists.
          psql -h ${DB_URL} -p ${DB_PORT} -U ${DB_ADMIN_USER} -tc '\du' | cut -d \| -f 1 | grep -qw ${DB_USER}
          if [[ $? -ne 0 ]]; then
            psql -h ${DB_URL} -p ${DB_PORT} -U ${DB_ADMIN_USER} -c "create user ${DB_USER} SUPERUSER password '${DB_PASSWORD}';"
          fi

          # check if contracts table in tks database already exists.
          psql -h ${DB_URL} -p ${DB_PORT} -U ${DB_ADMIN_USER} -d ${DB_NAME} -tc '\dt' | cut -d \| -f 2 | grep -qw contracts
          if [[ $? -ne 0 ]]; then
            echo """
              \c ${DB_NAME};
              CREATE TABLE contracts
              (
                  contractor_name character varying(50) COLLATE pg_catalog."default",
                  id uuid primary key,
                  available_services character varying(50)[] COLLATE pg_catalog."default",
                  updated_at timestamp with time zone,
                  created_at timestamp with time zone
              );
              CREATE UNIQUE INDEX idx_contractor_name ON contracts(contractor_name);
              ALTER TABLE contracts CLUSTER ON idx_contractor_name;
              INSERT INTO contracts(
                contractor_name, id, available_services, updated_at, created_at)
                VALUES ('tester', 'edcaa975-dde4-4c4d-94f7-36bc38fe7064', ARRAY['lma'], '2021-05-01'::timestamp, '2021-05-01'::timestamp);

              CREATE TABLE resource_quota
              (
                  id uuid primary key,
                  cpu bigint,
                  memory bigint,
                  block bigint,
                  block_ssd bigint,
                  fs bigint,
                  fs_ssd bigint,
                  contract_id uuid,
                  updated_at timestamp with time zone,
                  created_at timestamp with time zone
              );
            """ | psql -h ${DB_URL} -p ${DB_PORT} -U ${DB_ADMIN_USER}
          fi
{chart_source_home}/values.yaml

args:
  port: 9110
  dbUrl: postgresql.decapod-db.svc
  dbPort: 5432
  dbUser: tksuser
  dbPassword: password

db:
  adminUser: postgres
  adminPassword: password
  dbName: tks

 

컨테이너에서 필요한 값들(db superuser admin 과 password, db url, db port 등)은 env 로 전달한다. 참고로 postgresql 은 psql 로 접속할 때 패스워드를 전달하는 아규먼트가 없다. 대신 환경 변수로 다음과 같이 설정하면 패스워드를 사용하여 접속할 수 있다. ($ export PGPASSWORD=password)

  1. 먼저 DB 에 해당 database 가 존재하는 조회하고 없으면 database 를 생성한다.
  2. DB User 가 존재하는지 조회하고 없으면 새로운 DB User 를 생성한다.
  3. Table 이 존재하는지 조회하고 없으면 Table 을 생성한다. Table 을 생성하기 위해 psql 로 접속 시에 해당 database 에 접속할 수 있지만 (-d database명 옵션 사용), "\c database명" 으로 database 를 선택할 수 있다는 것을 보여주기 위해 이 부분을 추가하였다.

 

chart 를 배포하면 다음과 같이 job 이 실행됨을 알 수 있다.

$ kubectl get pods -n tks
NAME                               READY   STATUS      RESTARTS   AGE
tks-contract-h5468                 0/1     Completed   0          31h


$ kubectl logs tks-contract-h5468 -n tks
+ psql -h postgresql.decapod-db.svc -p 5432 -U postgres -lqt
+ cut -d '|' -f 1
+ grep -qw tks
+ [[ 0 -ne 0 ]]
+ psql -h postgresql.decapod-db.svc -p 5432 -U postgres -tc '\du'
+ cut -d '|' -f 1
+ grep -qw tksuser
+ [[ 0 -ne 0 ]]
+ psql -h postgresql.decapod-db.svc -p 5432 -U postgres -d tks -tc '\dt'
+ cut -d '|' -f 2
+ grep -qw contracts
+ [[ 0 -ne 0 ]]
반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

쿠버네티스에 서비스를 배포하기 위해 사용하는 대표적인 방법중에 하나가 바로 Helm chart 이다. 한마디로 말해서 Helm chart 는 쿠버네티스 용도의 패키징된 s/w 라 할 수 있다. Helm chart 문법은 go template 을 활용하였기 때문에 go template 을 안다면 조금 더 쉽게 이해할 수 있다.

여기서 설명하는 내용은 사전에 쿠버네티스 클러스터가 있으며 kubectl 로 api 에 접근할 수 있는 환경이 있다는 것을 가정한다.

그럼 도커 이미지를 가지고 쿠버네티스에 Helm 으로 배포할 수 있는 Helm chart 를 만들어 보자.

먼저 Helm (v3)을 설치한다.

$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh

$ helm version
version.BuildInfo{Version:"v3.4.0", GitCommit:"7090a89efc8a18f3d8178bf47d2462450349a004", GitTreeState:"clean", GoVersion:"go1.14.10"}

Helm 버전은 틀릴 수 있는데 v.3.0.0 이상이면 상관없다.

 

1. 기본 코드 생성하기

아무 디렉토리로 이동해서 아래의 명령어로 기본 코드를 생성한다. (여기서 예제는 gRpc 기반의 tks-contract 이라는 프로그램을 기반으로 했다)

$ helm create tks-contract

helm create 명령어는 tks-contract 디렉토리를 만들고 아래와 같은 구조로 디렉토리와 샘플 코드를 자동으로 만들어 준다. 그 구조는 다음과 같다.

$ tree tks-contract    
tks-contract
├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── service.yaml
│   ├── serviceaccount.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

3 directories, 10 files

Chart.yaml 은 chart 에 대한 기본적인 정보가 있는 파일이다. chart 명, chart 버전, chart 설명 등을 적을 수 있으며, 지금은 별로 바꿀 내용이 없다.

charts 디렉토리는 의존성을 관리한다. 예를 들면 DB를 이용하는 웹 애플리케이션 chart 를 만들 때 DB 가 설치되어야 한다면 여기에 chart 를 넣거다 chart repo 에 존재하는 chart 의 링크를 기술 할 수 있다. 이 것도 지금은 바꿀 내용이 없다.

templates 디렉토리는 가장 중요한 디렉토리다. 이 디렉토리 안에는 쿠버네티스 리소스 yaml 파일들이 (예를 들면, Deployment, Service 와 같은 리소스 정의 파일) 위치한다.

values.yaml 파일은 한마디로 변수들을 정의한 파일이다. templates 디렉토리 안에 있는 yaml 파일들의 특정 변수 값을 치환하고 싶을 때 값을 선언해 놓는 곳이다. 일반적으로 key: value 형태로 기술한다.

 

2. Template 작성 방법

먼저 간단한 templates 디렉토리 안의 service.yaml 을 변경해 보자.

apiVersion: v1
kind: Service
metadata:
  name: {{ include "tks-contract.fullname" . }}
  namespace: {{ .Values.namespace }}
  labels:
    {{- include "tks-contract.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: {{ .Values.args.port }}
      protocol: TCP
  selector:
    {{- include "tks-contract.selectorLabels" . | nindent 4 }}

go 템플릿을 사용하기 때문에 {{ }} 기호로 값을 치환한다는 것을 알 수 있다. 일반적으로 values.yaml 파일에 정의된 값을 치환할 수 있게 적혀있다. 가령 {{ .Values.namespace }} 는 values.yaml 파일의 namespace 를 키로 하는 값을 가져와서 치환하라는 의미이다. 실제 values.yaml 에는 아래와 같이 되어 있다.

namespace: tks
service:
  type: LoadBalancer
  port: 9110

values.yaml 파일의 service 아래의 type 을 키로 하는 값, 즉 LoadBalancer 라는 값을 가져오기 위해서는 {{ .Values.service.type }} 으로 적어주면 된다.

마지막 라인의 {{- include "tks-contract.selectorLabels" . | nindent 4 }} 와 같이 include 는 정의된 템플릿 호출을 의미한다. 즉 tks-contract.selectorLabels 라는 정의된 템플릿을 호출했다고 할 수 있다. 템플릿 정의들은 보통 templates 디렉토리 아래 _helpers.tpl 파일에 정의하며 define 으로 선언되어 있다. _helpers.tpl 파일의 일부를 살펴보자.

{{/*
Selector labels
*/}}
{{- define "tks-contract.selectorLabels" -}}
app.kubernetes.io/service: tks
app.kubernetes.io/name: {{ include "tks-contract.name" . }}
{{- end }}

{{/* */}} 은 주석처리이다. 그 아래에 {{- define "tks-contract.selectorLabels" -}} ... {{- end }} 은 템플릿 정의이다.

{{- include "tks-contract.selectorLabels" . | nindent 4 }} 에서 {{- 는 맨앞에 쓰였을 때 {{ }} 코드가 차지하는 영역의 줄바꿈과 공백을 없애라는 뜻이며 . 은 values.yaml 에 있는 모든 변수들을 아규먼트로 넘긴다는 뜻이다. 마지막으로 | 는 shell 에서의 파이프라인과 동일하고 nindent 4 는 결과를 프린트 할 때 공백 4개를 멀티라인으로 계속 들여쓰라는 의미이다.

정리해 보면 service.yaml 과 _helper.tpl 파일을 이 활용되어 아래와 같이 작동된다는 것을 알 수 있다.

## service.yaml
  selector:
    {{- include "tks-contract.selectorLabels" . | nindent 4 }}

+

## _helpers.tpl
{{- define "tks-contract.selectorLabels" -}}
app.kubernetes.io/service: tks
app.kubernetes.io/name: {{ include "tks-contract.name" . }}
{{- end }}


================ 결과 ===============
  selector:
    app.kubernetes.io/service: tks
    app.kubernetes.io/name: tks-contract

app.kubernetes.io/service: tks 와 app.kubernetes.io/name: tks-contract 가 들여쓰기 4 만큼 프린트 됐다. 물론 _helpers.tpl 내의 템플릿 정의를 찾아보면 {{ include "tks-contract.name" . }} 의 결과 값은 tks-contract 이다.

그리고 NOTES.txt 에 적힌 내용은 chart 가 배포되고 나서 터미널에 프린트된다.

 

3. 전체 소스 작성

코드를 원하는 값으로 수정하고 배포될 때의 최종 yaml 이 어떻게 될지 확인해보자.

먼저 Chart.yaml 이다.

$ vi tks-contract/Chart.yaml
apiVersion: v2
name: tks-contract
description: A Helm chart for tks-contract
type: application
version: 0.1.0
appVersion: 0.1.0

_helpers.tpl 은 selectorLabels 를 원하는 값을 넣기 위해서 49 라인만 수정하였다.

$ vi tks-contract/_helpers.tpl
...
{{/*
Selector labels
*/}}
{{- define "tks-contract.selectorLabels" -}}
app.kubernetes.io/service: tks
app.kubernetes.io/name: {{ include "tks-contract.name" . }}
{{- end }}
...

deployment.yaml 은 container 의 args 값을 추가했다.

$ vi tks-contract/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "tks-contract.fullname" . }}
  namespace: {{ .Values.namespace }}
  labels:
    {{- include "tks-contract.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "tks-contract.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      labels:
        {{- include "tks-contract.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "tks-contract.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }}
      containers:
        - name: {{ .Chart.Name }}
          securityContext:
            {{- toYaml .Values.securityContext | nindent 12 }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: tks-contract
              containerPort: {{ .Values.args.port }}
              protocol: TCP
          command:
            - /app/server
          args: [
            "-port", "{{ .Values.args.port }}",
            "-dbhost", "{{ .Values.args.dbUrl }}",
            "-dbport", "{{ .Values.args.dbPort }}",
            "-dbuser", "{{ .Values.args.dbUser }}",
            "-dbpassword", "{{ .Values.args.dbPassword }}",
            "-info-address", "{{ .Values.args.tksInfoAddress }}",
            "-info-port", "{{ .Values.args.tksInfoPort }}"
          ]
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

조건절은 {{- if }} 혹은 {{- if not }} {{ end }} 로 사용할 수 있고 {{- with 키 }} {{ end }} 는 with 절 안에서는 구조상 해당 키 아래의 값들을 사용하겠다는 의미이다. {{- toYaml 키 }} 는 키의 값을 그대로 Yaml 로 프린트 해 준다.

nodeSelector 를 예를 들어 보자.

## deployment.yaml
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}

+

## values.yaml
nodeSelector:
  taco-tks: enabled


============ 결과 =================
      nodeSelector:
        taco-tks: enabled

values.yaml 에 nodeSelector 키 아래에 taco-tks: enabled 라는 값을 그대로 toYaml 로 프린트 한다.

service.yaml 전체는 다음과 같다.

$ vi tks-contract/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ include "tks-contract.fullname" . }}
  namespace: {{ .Values.namespace }}
  labels:
    {{- include "tks-contract.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: {{ .Values.args.port }}
      protocol: TCP
  selector:
    {{- include "tks-contract.selectorLabels" . | nindent 4 }}

values.yaml 에 전체 값을 넣는다.

$ vi tks-contract/values.yaml

replicaCount: 1

namespace: tks

image:
  repository: docker.io/seungkyu/tks-contract
  pullPolicy: Always
  tag: "latests"

imagePullSecrets: []
nameOverride: "tks-contract"
fullnameOverride: "tks-contract"

serviceAccount:
  create: true
  annotations: {}
  name: "tks-info"

args:
  port: 9110
  dbUrl: postgresql.decapod-db.svc
  dbPort: 5432
  dbUser: tksuser
  dbpassword: tkspassword
  tksInfoAddress: tks-info.tks.svc
  tksInfoPort: 9110

podAnnotations: {}

podSecurityContext: {}
  # fsGroup: 2000

securityContext: {}
  # capabilities:
  #   drop:
  #   - ALL
  # readOnlyRootFilesystem: true
  # runAsNonRoot: true
  # runAsUser: 1000

service:
  type: LoadBalancer
  port: 9110

ingress:
  enabled: false
  annotations: {}
  hosts:
    - host: chart-example.local
      paths: []
  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local

resources: {}
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube. If you do want to specify resources, uncomment the following
  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
  # limits:
  #   cpu: 100m
  #   memory: 128Mi
  # requests:
  #   cpu: 100m
  #   memory: 128Mi

autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 100
  targetCPUUtilizationPercentage: 80
  # targetMemoryUtilizationPercentage: 80

nodeSelector:
  taco-tks: enabled

tolerations: []

affinity: {}

마지막으로 배포후 프린트될 내용은 NOTES.txt 을 수정한다.

$ vi tks-contract/templates/NOTES.txt
TKS-Contract
{{- if contains "LoadBalancer" .Values.service.type }}
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch the status of by running 'kubectl get --namespace {{ .Values.namespace }} svc -w {{ include "tks-contract.fullname" . }}'
  export SERVICE_IP=$(kubectl get svc --namespace {{ .Values.namespace }} {{ include "tks-contract.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
  gRPC Call => $SERVICE_IP:{{ .Values.service.port }}
{{- end }}

 

4. 결과 확인

작성된 chart 는 아래 명령어로 최종 yaml 이 어떻게 변환되어 배포되는지 dry-run 으로 확인할 수 있다.

$ helm upgrade -i tks-contract ./tks-contract --dry-run --debug

---
# Source: tks-contract/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tks-info
  labels:
    helm.sh/chart: tks-contract-0.1.0
    app.kubernetes.io/service: tks
    app.kubernetes.io/name: tks-contract
    app.kubernetes.io/version: "0.1.0"
    app.kubernetes.io/managed-by: Helm
---
# Source: tks-contract/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: tks-contract
  namespace: tks
  labels:
    helm.sh/chart: tks-contract-0.1.0
    app.kubernetes.io/service: tks
    app.kubernetes.io/name: tks-contract
    app.kubernetes.io/version: "0.1.0"
    app.kubernetes.io/managed-by: Helm
spec:
  type: LoadBalancer
  ports:
    - port: 9110
      targetPort: 9110
      protocol: TCP
  selector:
    app.kubernetes.io/service: tks
    app.kubernetes.io/name: tks-contract
---
# Source: tks-contract/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tks-contract
  namespace: tks
  labels:
    helm.sh/chart: tks-contract-0.1.0
    app.kubernetes.io/service: tks
    app.kubernetes.io/name: tks-contract
    app.kubernetes.io/version: "0.1.0"
    app.kubernetes.io/managed-by: Helm
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/service: tks
      app.kubernetes.io/name: tks-contract
  template:
    metadata:
      labels:
        app.kubernetes.io/service: tks
        app.kubernetes.io/name: tks-contract
    spec:
      serviceAccountName: tks-info
      securityContext:
        {}
      containers:
        - name: tks-contract
          securityContext:
            {}
          image: "docker.io/seungkyu/tks-contract:latests"
          imagePullPolicy: Always
          ports:
            - name: tks-contract
              containerPort: 9110
              protocol: TCP
          command:
            - /app/server
          args: [
            "-port", "9110",
            "-dbhost", "postgresql.decapod-db.svc",
            "-dbport", "5432",
            "-dbuser", "tksuser",
            "-dbpassword", "",
            "-info-address", "tks-info.tks.svc",
            "-info-port", "9110"
          ]
          resources:
            {}
      nodeSelector:
        taco-tks: enabled

NOTES:
TKS-Contract
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch the status of by running 'kubectl get --namespace tks svc -w tks-contract'
  export SERVICE_IP=$(kubectl get svc --namespace tks tks-contract --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}")
  gRPC Call => $SERVICE_IP:9110
반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

이전 글은 Cluster API 가 어떤 것이고 어떻게 동작하는지를 알아봤다면 이번에는 AWS 에 Cluster API를 이용하여 Kubernetes 를 쉽게 설치하는 방법을 설명한다. 사실 AWS를 어느 정도 알고있어야 (특히 VPC, Subnet, Route Table, Nat Gateway, Internet Gateway 같은 네트워크) 해당 내용들을 이해하기가 쉽기 때문에 Cluster API 로 구현되는 최종 Kubernetes Cluster 구성에 대해서 먼저 설명하겠다.

 

 

특정 리전 (여기서는 서울)에 3개의 가용존(AZ1~3)에 Kubernetes Master 와 Node 가 설치되게 된다. 하나의 AZ에는 public subnet 과 private subnet 이 만들어지며 public subnet은 인터넷과 통하는 Internet Gateway 와 연결되어 있다. 네트워크 통신을 위해서는 Route Table 을 통해서 default gateway (0.0.0.0/0)를 설정해야 하는데 public subnet 은 Internet Gateway 를 private subnet 은 EIP 가 할당되어 있는 NAT gateway 를 설정한다.

 

AWS 에서는 Internet Gateway 와 연결되어 있는데 subnet 을 public subnet 으로 명명하며 public IP 가 할당되는 자원들은 모두 public subnet 에 생성한다. 그 외의 subnet 은 private subnet 이라 명명하는데, 이 때 외부 outbound 연결을 하기 위해서는 public subnet 에 NAT Gateway 를 만들어 활용한다. 보라색 점선이 Internet Gateway 와 NAT Gateway 로 default gateway 를 설정한 내용을 보여준다.

 

요약하면 AWS 에서의 네트워크는 Subnet, Route table, Gateway (Internet 혹은 NAT) 로 구성된다.

 

모든 VM (Control plane과 Node) instance는 private subnet에 생성되어 외부에서 직접 VM 으로 접속하는 경로를 차단한다. 필요에 따라 외부의 Inbound 를 트래픽을 가능하게 하려면 Load Balancer 를 통해서 연결한다(물론 Internal LB도 가능하다). Load Balancer 가 private subent 의 VM 에 연결되기 위해서는 public subnet 이 LB에 등록되어야 한다. 다이어그램에서 이를 표현한 것이 초록색 점선이다. LB 와 Control plane 을 연결한 것은 API Server 가 LB 에 등록되어 로드밸랜싱된다는 의미이며, 고객 서비스의 연결은 Node VM 에 연결될 수 있다. 이 때 Service 리소스의 Type 을 LoadBalancer 로 지정하면 자동으로 연결된다.

 

이 기능들이 가능한 이유는 Kubernetes 에 Cloud provider controller 가 내장되어 있기 때문이다. 하지만 이 기능은 deprecated 될 예정으로 버전 1.23 부터는 소스가 분리될 예정이다.

 

또 한가지 일반적인 방법으로 설치하면 그림의 내용과 같이 모든 Node VM 이 AZ1 의 private subnet 에 몰려서 생성된다. 이는 가용성에 문제가 될 수 있으며 이를 해결하기 위해서는 아직은 experimental 버전인 MachinePool 기능을 사용해야 한다.

이제 본격적으로 Cluster API 로 Kubernetes Cluster 를 설치해 보자.

 

이전 글을 보면 Management cluster 에 Cluster API Controller 를 설치한 후 Custom Resource (CR) 를 생성하면 자동으로 Workload cluster 가 생성된다는 것을 설명하였다. 이를 잘 기억해 두고 Management cluster 는 존재한다는 가정하에 시작한다(인터넷에서 조회해보면 Kind 로 쉽게 Management cluster 를 구성하는 방법을 찾을 수 있다).

 

1. clusterctl 설치

clusterctl 은 Managed cluster 에 Cluster API controller 를 설치하고 Workload cluster 를 위한 CR 생성을 도와주는 도구이다.

$ curl -L https://github.com/kubernetes-sigs/cluster-api/releases/download/v0.3.20/clusterctl-linux-amd64 -o clusterctl
$ chmod +x ./clusterctl
$ mv ./clusterctl /usr/local/bin/clusterctl
$ clusterctl version
=== output ===
clusterctl version: &version.Info{Major:"0", Minor:"3", GitVersion:"v0.3.20", GitCommit:"ea9dc4bdc2a9938325aab3817ece3e654873aaab", GitTreeState:"clean", BuildDate:"2021-06-30T22:10:51Z", GoVersion:"go1.13.15", Compiler:"gc", Platform:"linux/amd64"}

 

2. clusterawsadm 설치

clusterawsadm 은 aws 에 필요한 Role 과 Policy 를 자동으로 생성해 주는 도구이다.

$ curl -L https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/download/v0.6.6/clusterawsadm-linux-amd64 -o clusterawsadm
$ chmod +x clusterawsadm
$ mv ./clusterawsadm /usr/local/bin/clusterawsadm
$ clusterawsadm version
=== output ===
clusterawsadm version: &version.Info{Major:"0", Minor:"6", GitVersion:"v0.6.6-4-d4593daa95fb96-dirty", GitCommit:"d4593daa95fb961be91dc6db869f26ca4359ebc0", GitTreeState:"dirty", BuildDate:"2021-06-01T20:05:33Z", GoVersion:"go1.13.15", AwsSdkVersion:"v1.36.26", Compiler:"gc", Platform:"linux/amd64"}

 

3. awscli 및 jq 설치

$ curl -L https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.2.13.zip -o awscliv2.zip
$ unzip awscliv2.zip
$ ./aws/install
$ aws --version
=== output ===
aws-cli/2.2.13 Python/3.8.8 Linux/4.4.0-87-generic exe/x86_64.ubuntu.16 prompt/off

$ mkdir -p ~/.aws
$ vi ~/.aws/credentials
[default]
aws_access_key_id=<< access_key_id >>
aws_secret_access_key=<< secret_access_key >>

$ vi ~/.aws/config
[default]
region = ap-northeast-2

 

4. 사전 작업

clusterawsadm 을 활용하여 IAM Role 과 Policy 를 생성한다.

## 설정이 안되어 있을 때 환경 변수로 설정한다.
$ export AWS_REGION=ap-northeast-2
$ export AWS_ACCESS_KEY_ID=<< access_key_id >>
$ export AWS_SECRET_ACCESS_KEY=<< secret_access_key >>

$ clusterawsadm bootstrap iam create-cloudformation-stack

Workload cluster VM 에 접속할 key pair 를 Import 한다.

$ aws ec2 import-key-pair \
  --key-name capi-seungkyu \
  --public-key-material fileb://~/.ssh/id_rsa.pub

 

5. Management cluster 생성

clusterctl 생성에 필요한 환경 변수를 다음과 같이 설정한다.

$ clusterawsadm bootstrap credentials encode-as-profile
=== output ===
<< crendentials >>

$ vi env.sh
export AWS_REGION=ap-northeast-2
export AWS_ACCESS_KEY_ID=<< access_key_id >>
export AWS_SECRET_ACCESS_KEY=<< secret_access_key >>
export AWS_B64ENCODED_CREDENTIALS=<< crendentials >>

$ source ./env.sh

clusterctl init 명령어를 사용하여 Management cluster 를 생성한다.

$ clusterctl init --core cluster-api:v0.3.20 --infrastructure aws:v0.6.6 --bootstrap kubeadm:v0.3.20 --control-plane kubeadm:v0.3.20 -v5
=== output ===
...
Fetching providers
Installing cert-manager
Waiting for cert-manager to be available...
...
Installing Provider="cluster-api" Version="v0.3.20" TargetNamespace="capi-system"
...
Installing Provider="bootstrap-kubeadm" Version="v0.3.20" TargetNamespace="capi-kubeadm-bootstrap-system"
...
Installing Provider="control-plane-kubeadm" Version="v0.3.20" TargetNamespace="capi-kubeadm-control-plane-system"
...
Installing Provider="infrastructure-aws" Version="v0.6.6" TargetNamespace="capa-system"

Your management cluster has been initialized successfully!

You can now create your first workload cluster by running the following:

  clusterctl config cluster [name] --kubernetes-version [version] | kubectl apply -f -

 

6. Workload cluster 생성

필요한 환경변수를 설정하고 clusterctl config 명령어를 이용하여 CR 파일을 생성한다.

$ vi env-workload.sh
export AWS_CONTROL_PLANE_MACHINE_TYPE=t3.large
export AWS_NODE_MACHINE_TYPE=t3.large
export AWS_SSH_KEY_NAME=capi-seungkyu

$ source ./env-workload.sh

$ clusterctl config cluster capi-quickstart \
  -n capi-quickstart \
  --kubernetes-version v1.20.5 \
  --control-plane-machine-count=3 \
  --worker-machine-count=3 \
  > capi-quickstart.yaml

capi-quickstart.yaml 을 확인하면 아래와 같다.

$ cat capi-quickstart.yaml
---
apiVersion: cluster.x-k8s.io/v1alpha3
kind: Cluster
metadata:
  name: capi-quickstart
  namespace: capi-quickstart
spec:
  clusterNetwork:
    pods:
      cidrBlocks:
      - 192.168.0.0/16
  controlPlaneRef:
    apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
    kind: KubeadmControlPlane
    name: capi-quickstart-control-plane
  infrastructureRef:
    apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
    kind: AWSCluster
    name: capi-quickstart
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
kind: AWSCluster
metadata:
  name: capi-quickstart
  namespace: capi-quickstart
spec:
  region: ap-northeast-2
  sshKeyName: capi-seungkyu
---
apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
kind: KubeadmControlPlane
metadata:
  name: capi-quickstart-control-plane
  namespace: capi-quickstart
spec:
  infrastructureTemplate:
    apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
    kind: AWSMachineTemplate
    name: capi-quickstart-control-plane
  kubeadmConfigSpec:
    clusterConfiguration:
      apiServer:
        extraArgs:
          cloud-provider: aws
      controllerManager:
        extraArgs:
          cloud-provider: aws
    initConfiguration:
      nodeRegistration:
        kubeletExtraArgs:
          cloud-provider: aws
        name: '{{ ds.meta_data.local_hostname }}'
    joinConfiguration:
      nodeRegistration:
        kubeletExtraArgs:
          cloud-provider: aws
        name: '{{ ds.meta_data.local_hostname }}'
  replicas: 3
  version: v1.20.5
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
kind: AWSMachineTemplate
metadata:
  name: capi-quickstart-control-plane
  namespace: capi-quickstart
spec:
  template:
    spec:
      iamInstanceProfile: control-plane.cluster-api-provider-aws.sigs.k8s.io
      instanceType: t3.large
      sshKeyName: capi-seungkyu
---
apiVersion: cluster.x-k8s.io/v1alpha3
kind: MachineDeployment
metadata:
  name: capi-quickstart-md-0
  namespace: capi-quickstart
spec:
  clusterName: capi-quickstart
  replicas: 3
  selector:
    matchLabels: null
  template:
    spec:
      bootstrap:
        configRef:
          apiVersion: bootstrap.cluster.x-k8s.io/v1alpha3
          kind: KubeadmConfigTemplate
          name: capi-quickstart-md-0
      clusterName: capi-quickstart
      infrastructureRef:
        apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
        kind: AWSMachineTemplate
        name: capi-quickstart-md-0
      version: v1.20.5
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3
kind: AWSMachineTemplate
metadata:
  name: capi-quickstart-md-0
  namespace: capi-quickstart
spec:
  template:
    spec:
      iamInstanceProfile: nodes.cluster-api-provider-aws.sigs.k8s.io
      instanceType: t3.large
      sshKeyName: capi-seungkyu
---
apiVersion: bootstrap.cluster.x-k8s.io/v1alpha3
kind: KubeadmConfigTemplate
metadata:
  name: capi-quickstart-md-0
  namespace: capi-quickstart
spec:
  template:
    spec:
      joinConfiguration:
        nodeRegistration:
          kubeletExtraArgs:
            cloud-provider: aws
          name: '{{ ds.meta_data.local_hostname }}'

 

해당 CR 은 아래 다이어그램 구조로 매칭된다.

 

 

Cluster API 는 공통이 Abstract Class 와 같이 기본을 정의해 놓았으며, Provider 는 실제 Cloud 에 맞는 구현을 정의하였다. AWSCluster 에는 VPC, subnet, Route table 등과 같은 인프라 생성 정보를 가지고 있고 AWSMachineTemplate 은 Kubernetes Control Plane 에 대한 정보를 AWSMachineTemplate 에는 Node 정보를 갖고 있다.

 

CR 을 배포하여 Workload cluster 를 생성한다.

$ kubectl create ns capi-quickstart

$ kubectl apply -f capi-quickstart.yaml
=== output ===
cluster.cluster.x-k8s.io/capi-quickstart created
awscluster.infrastructure.cluster.x-k8s.io/capi-quickstart created
kubeadmcontrolplane.controlplane.cluster.x-k8s.io/capi-quickstart-control-plane created
awsmachinetemplate.infrastructure.cluster.x-k8s.io/capi-quickstart-control-plane created
machinedeployment.cluster.x-k8s.io/capi-quickstart-md-0 created
awsmachinetemplate.infrastructure.cluster.x-k8s.io/capi-quickstart-md-0 created
kubeadmconfigtemplate.bootstrap.cluster.x-k8s.io/capi-quickstart-md-0 created

kubectl 명령어를 위해 아래와 같이 kubeconfig 를 생성한다.

$ clusterctl get kubeconfig capi-quickstart -n capi-quickstart > capi-quickstart.kubeconfig

한가지 중요한 부분은 Cluster API 는 Network 모듈이나, CSI 를 설치해 주지는 않는다. 이는 추가로 설치해야 한다.

$ kubectl --kubeconfig=./capi-quickstart.kubeconfig \
  apply -f https://docs.projectcalico.org/v3.19/manifests/calico.yaml

네트워크 모듈까지 다 설치하면 정상적으로 Kubernetes Cluster 가 설치된 것을 볼 수 있다.

$ kubectl get kubeadmcontrolplane -n capi-quickstart
=== output ===
NAME                            INITIALIZED   API SERVER AVAILABLE   VERSION   REPLICAS   READY   UPDATED   UNAVAILABLE
capi-quickstart-control-plane   true          true                   v1.20.5   3          3       3


$ kubectl --kubeconfig=mycluster.kubeconfig get nodes
=== output ===
NAME                                              STATUS   ROLES                  AGE   VERSION
ip-10-0-143-5.ap-northeast-2.compute.internal     Ready    control-plane,master   23h   v1.20.5
ip-10-0-164-198.ap-northeast-2.compute.internal   Ready    <none>                 23h   v1.20.5
ip-10-0-222-84.ap-northeast-2.compute.internal    Ready    control-plane,master   23h   v1.20.5
ip-10-0-255-19.ap-northeast-2.compute.internal    Ready    <none>                 23h   v1.20.5
ip-10-0-68-113.ap-northeast-2.compute.internal    Ready    <none>                 23h   v1.20.5
ip-10-0-80-79.ap-northeast-2.compute.internal     Ready    control-plane,master   23h   v1.20.5

 

반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

AWS 에서 Kubernetes 사용하는 방법은 Managed Service 인 EKS 서비스를 신청/설치 방법과 일반 VM 에 Kubernetes 를 설치하는 방법이 있다.

VM 에 Kubernetes 를 설치하는 방법은 VM 을 생성하고 필요한 Security Group 을 지정하고 Muti Control plane 에 ELB 를 연결하여 다중 Master 로 사용가능 하도록 세팅해야할 내용이 제법 있다. Kubernetes 자체 설치도 kubeadm 이나 kubespray 같은 툴을 사용해서 설치한다. - 역시 쉽지 않은 내용임에 분명하다.

 

Cluster API 란 ?

지금 소개하는 방법은 Kubernetes Cluster Lifecycle SIG 에서 개발하고 있는 Cluster API 로 Operator Pattern 을 활용하여 Kubernetes 를 설치하는 방법이다. Cluster API 란 Management Kubernetes Cluster 에 Custom Controller 를 설치하고 Custom Resource 를 생성하면 자동으로 Cloud 에 VM 을 생성하고 Kubernetes 를 설치하는 방법이다. Controller 로 관리하기 때문에 VM 이 죽으면 다시 살려주는 장점이 있다. Kubernetes 가 설치된 VM 이 다운되었을 때 Self healing 으로 Recovering 해준다니 정말 멋진 아이디어인 것 같다.

Cluster API 의 시작은 2년 정도 전부터 시작되었는데 초장기에는 PoC 정도였다면 지금은 어느 정도 안정화 되어 사용하기에 문제가 없다.

개념을 보면 Management Kubernetes Cluster 를 설치(Kind, Minikube 등도 가능)하고 여기에 필요한 여러 Custom Controller 를 실행시킨다. 그리고 나서 Custom Resource 를 생성하면 지정된 Cloud 에 VM 을 생성하고 Workload Kubernetes Cluster (고객이 사용할 클러스터) 를 자동으로 생성해 주는 방법이다.

 

Cluster API 구성도

 

그림에서 보면 좌측 맨 위에 Cluster API Controller 가 있다. Cluster API Controller 는 마치 java class 로 치면 Abstraction Class 의 역활과 동일하다. Cluster Pod Network, Control Plane VM 갯수, Node VM 갯수 등을 관리한다.

이렇게 정의를 하면 Boostrap provider Controller 에서 kubeadm 으로 어떻게 Kubernetes 를 설치할 지를 관리한다.

Infrastructure provider Controller 에서는 AWS, Azure, GCP 와 같이 Public Cloud 의 자원 생성을 관리한다.

마지막으로 Control Plane provider Controller 특별하게 Control Plane 의 설치를 관리한다.

우측 위는 Custom Resource 를 보여주고 있다.

 

Cluster API Provider Controller 와 Infrastructure Provider Controller 의 상관 관계를 보면 다음과 같다.

 

Cluster API 는 Infrastructure Provider 에게 이런 식으로 구현을 해야 한다고 알려주는 가이드라고 보면 된다.

이제 세부적으로 하나씩 살펴보자.

 

Cluster API Controller

Cluster API Controller 는 cluster 의 기본 정보를 관리하는 Controller 이다. 이 Controller 가 관리하는 Custom Resource 는 다음과 같이 4개가 있다.

  • clusters
  • machinedeployments
  • machinehealthchecks
  • machines

이 중에서 machinehealthchecks 는 AWS Provider 에서는 사용하고 있지 않으므로 이를 제외하고 나머지 3가지만 살펴보겠다.

clusters CR(Custom Resource) 는 다음과 같은 정보를 가지고 있다.

  • Cluster Pod Network 정보
  • Kubernetes API url 정보
  • Control Plane 을 생성하는 CR 정보
  • Cloud Infrastructure 로 사용되는 CR 정보
  Cluster Network:
    Pods:
      Cidr Blocks:
        192.168.0.0/16
  Control Plane Endpoint:
    Host:  capi-quickstart-apiserver-479433786.ap-northeast-2.elb.amazonaws.com
    Port:  6443
  Control Plane Ref:
    API Version:  controlplane.cluster.x-k8s.io/v1alpha3
    Kind:         KubeadmControlPlane
    Name:         capi-quickstart-control-plane
    Namespace:    capi-quickstart
  Infrastructure Ref:
    API Version:  infrastructure.cluster.x-k8s.io/v1alpha3
    Kind:         AWSCluster
    Name:         capi-quickstart
    Namespace:    capi-quickstart

 

machinedeployments CR 의 정보는 Kubernetes Worker Node 에 대한 일반적인 내용이며 아래의 내용을 포함한다.

  • Bootstrap 으로 사용될 Kubeadm Config Template
  • Infrastructure Provider 에 대한 VM 정보 Template
  • Cluster name 및 Kubernetes Version 정보
  Cluster Name:               capi-quickstart
  Min Ready Seconds:          0
  Progress Deadline Seconds:  600
  Replicas:                   3
  Revision History Limit:     1
  Selector:
    Match Labels:
      cluster.x-k8s.io/cluster-name:     capi-quickstart
      cluster.x-k8s.io/deployment-name:  capi-quickstart-md-0
  Strategy:
    Rolling Update:
      Max Surge:        1
      Max Unavailable:  0
    Type:               RollingUpdate
  Template:
    Metadata:
      Labels:
        cluster.x-k8s.io/cluster-name:     capi-quickstart
        cluster.x-k8s.io/deployment-name:  capi-quickstart-md-0
    Spec:
      Bootstrap:
        Config Ref:
          API Version:  bootstrap.cluster.x-k8s.io/v1alpha3
          Kind:         KubeadmConfigTemplate
          Name:         capi-quickstart-md-0
      Cluster Name:     capi-quickstart
      Infrastructure Ref:
        API Version:  infrastructure.cluster.x-k8s.io/v1alpha3
        Kind:         AWSMachineTemplate
        Name:         capi-quickstart-md-0
      Version:        v1.20.5

 

machines CR 은 Kubernetes Control Plane VM 별 정보와 Kubernetes Worker Node VM 별 정보를 갖고 있는데 둘다 아래의 정보를 동일하게 갖고 있다.

  • Boostrap 으로 KubeadmConfig CR 정보
  • Infrastructure 로서 AWSMachine CR 정보
  Bootstrap:
    Config Ref:
      API Version:     bootstrap.cluster.x-k8s.io/v1alpha3
      Kind:            KubeadmConfig
      Name:            capi-quickstart-md-0-7f4xk
      Namespace:       capi-quickstart
      UID:             7b095ef9-3380-45cc-950e-9bce8602f954
    Data Secret Name:  capi-quickstart-md-0-7f4xk
  Cluster Name:        capi-quickstart
  Infrastructure Ref:
    API Version:  infrastructure.cluster.x-k8s.io/v1alpha3
    Kind:         AWSMachine
    Name:         capi-quickstart-md-0-dcdcx
    Namespace:    capi-quickstart
    UID:          3383bd5b-1040-4577-a690-741c556d1076
  Provider ID:    aws:///ap-northeast-2a/i-090230ea1d12065ce
  Version:        v1.20.5

 

Bootstrap Provider Controller

Bootstrap provider controller 는 Kubernetes 를 설치하는 kubeadm 관련 정보를 가지고 있다.

  • kubeadmconfigs
  • kubeadmconfigtemplates

kubeadmconfigs 는 kubeadm 으로 kubernetes 를 설치할 때 사용하는 정보로서 Control Plane VM 별 정보와 Work Node VM별 정보를 갖고 있다.

  • Cluster Configuration 으로 provider 정보를 갖고 있다.
  • Join Configuration 으로 Token 정보를 갖고 있다.
  Cluster Configuration:
    API Server:
      Extra Args:
        Cloud - Provider:  aws
    Controller Manager:
      Extra Args:
        Cloud - Provider:  aws
    Dns:
    Etcd:
    Networking:
    Scheduler:
  Join Configuration:
    Control Plane:
      Local API Endpoint:
        Advertise Address:
        Bind Port:          0
    Discovery:
      Bootstrap Token:
        API Server Endpoint:  capi-quickstart-apiserver-479433786.ap-northeast-2.elb.amazonaws.com:6443
        Ca Cert Hashes:
          sha256:de8ef58efd55489531f681066eb77cec21a7d7f8e2a04eb41a06ad38b18d066e
        Token:                        xxxx.xxxxxxxxxxx
        Unsafe Skip CA Verification:  false
    Node Registration:
      Kubelet Extra Args:
        Cloud - Provider:  aws
      Name:                {{ ds.meta_data.local_hostname }}

 

kubeadmconfigtemplates 는 Worker Node 가 join 한 template 정보로 Node 명을 가진다.

  Template:
    Spec:
      Join Configuration:
        Node Registration:
          Kubelet Extra Args:
            Cloud - Provider:  aws
          Name:                {{ ds.meta_data.local_hostname }}

 

Infrastructure Provider Controller

Infrastructure Provider Controller 는 각각의 Cloud 의 API 를 활용하여 리소스를 관리하는 Controller 로 aws 의 경우에는 아래의 정보를 가진다.

  • awsclustercontrolleridentities
  • awsclusterroleidentities
  • awsclusters
  • awsclusterstaticidentities
  • awsfargateprofiles
  • awsmachinepools
  • awsmachines
  • awsmachinetemplates
  • awsmanagedclusters
  • awsmanagedmachinepools

이 중에서 awsclusters, awsmachines, awsmachinetemplate 3가지만 살펴본다.

awsclusters 는 aws 에 설치된 Kubernetes workload cluster 에 대한 정보를 갖고 있다.

  Bastion:
    Allowed CIDR Blocks:
      0.0.0.0/0
    Enabled:  false
  Control Plane Endpoint:
    Host:  capi-quickstart-apiserver-479433786.ap-northeast-2.elb.amazonaws.com
    Port:  6443
  Identity Ref:
    Kind:  AWSClusterControllerIdentity
    Name:  default
  Network Spec:
    Cni:
      Cni Ingress Rules:
        Description:  bgp (calico)
        From Port:    179
        Protocol:     tcp
        To Port:      179
        Description:  IP-in-IP (calico)
        From Port:    -1
        Protocol:     4
        To Port:      65535
    Subnets:
      Availability Zone:  ap-northeast-2a
      Cidr Block:         10.0.0.0/20
      Id:                 subnet-04e972e6bfcb98336
      Is Public:          true
      Nat Gateway Id:     nat-0d456fe6c4dd2ce3d
      Route Table Id:     rtb-0dfccca96ffc00732
      Tags:
        Name:                                                          capi-quickstart-subnet-public-ap-northeast-2a
        kubernetes.io/cluster/capi-quickstart:                         shared
        kubernetes.io/role/elb:                                        1
        sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart:  owned
        sigs.k8s.io/cluster-api-provider-aws/role:                     public
      Availability Zone:                                               ap-northeast-2a
      Cidr Block:                                                      10.0.64.0/18
      Id:                                                              subnet-06f2be3eedb46d622
      Is Public:                                                       false
      Route Table Id:                                                  rtb-00090ad5d1de60b20
      Tags:
        Name:                                                          capi-quickstart-subnet-private-ap-northeast-2a
        kubernetes.io/cluster/capi-quickstart:                         shared
        kubernetes.io/role/internal-elb:                               1
        sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart:  owned
        sigs.k8s.io/cluster-api-provider-aws/role:                     private
      Availability Zone:                                               ap-northeast-2b
      Cidr Block:                                                      10.0.16.0/20
      Id:                                                              subnet-02a9eed4c602fd3a2
      Is Public:                                                       true
      Nat Gateway Id:                                                  nat-050244cb7c9cff368
      Route Table Id:                                                  rtb-020e9366a2da25625
      Tags:
        Name:                                                          capi-quickstart-subnet-public-ap-northeast-2b
        kubernetes.io/cluster/capi-quickstart:                         shared
        kubernetes.io/role/elb:                                        1
        sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart:  owned
        sigs.k8s.io/cluster-api-provider-aws/role:                     public
      Availability Zone:                                               ap-northeast-2b
      Cidr Block:                                                      10.0.128.0/18
      Id:                                                              subnet-03880f2f847b3c21e
      Is Public:                                                       false
      Route Table Id:                                                  rtb-06b52ab77da5c5f2c
      Tags:
        Name:                                                          capi-quickstart-subnet-private-ap-northeast-2b
        kubernetes.io/cluster/capi-quickstart:                         shared
        kubernetes.io/role/internal-elb:                               1
        sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart:  owned
        sigs.k8s.io/cluster-api-provider-aws/role:                     private
      Availability Zone:                                               ap-northeast-2c
      Cidr Block:                                                      10.0.32.0/20
      Id:                                                              subnet-09ccabe6dd862df6a
      Is Public:                                                       true
      Nat Gateway Id:                                                  nat-030068539013d5c64
      Route Table Id:                                                  rtb-0973a222fc772ff76
      Tags:
        Name:                                                          capi-quickstart-subnet-public-ap-northeast-2c
        kubernetes.io/cluster/capi-quickstart:                         shared
        kubernetes.io/role/elb:                                        1
        sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart:  owned
        sigs.k8s.io/cluster-api-provider-aws/role:                     public
      Availability Zone:                                               ap-northeast-2c
      Cidr Block:                                                      10.0.192.0/18
      Id:                                                              subnet-0be3353fd3b6f1637
      Is Public:                                                       false
      Route Table Id:                                                  rtb-0854effe818750d33
      Tags:
        Name:                                                          capi-quickstart-subnet-private-ap-northeast-2c
        kubernetes.io/cluster/capi-quickstart:                         shared
        kubernetes.io/role/internal-elb:                               1
        sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart:  owned
        sigs.k8s.io/cluster-api-provider-aws/role:                     private
    Vpc:
      Availability Zone Selection:    Ordered
      Availability Zone Usage Limit:  3
      Cidr Block:                     10.0.0.0/16
      Id:                             vpc-070a545cb4b967dd7
      Internet Gateway Id:            igw-03a4ee0ca0f8d0f72
      Tags:
        Name:                                                          capi-quickstart-vpc
        sigs.k8s.io/cluster-api-provider-aws/cluster/capi-quickstart:  owned
        sigs.k8s.io/cluster-api-provider-aws/role:                     common
  Region:                                                              ap-northeast-2
  Ssh Key Name:                                                        capi-seungkyu

 

awsmachinetemplates 은 aws vm spec 에 대한 정보를 template 으로 갖고 있다. template 은 control plane 과 worker node 각 1개씩 가진다.

 

control plane template

  Template:
    Spec:
      Iam Instance Profile:  control-plane.cluster-api-provider-aws.sigs.k8s.io
      Instance Type:         t3.large
      Ssh Key Name:          capi-seungkyu

worker node template

  Template:
    Spec:
      Iam Instance Profile:  nodes.cluster-api-provider-aws.sigs.k8s.io
      Instance Type:         t3.large
      Ssh Key Name:          capi-seungkyu

 

awsmachines 은 awsmachinetemplates 으로 연결된 control plane 과 worker node 별 aws VM 에 대한 정보를 갖는다.

 

contol plane vm1

  Ami:
  Cloud Init:
    Secure Secrets Backend:  secrets-manager
  Iam Instance Profile:      control-plane.cluster-api-provider-aws.sigs.k8s.io
  Instance ID:               i-0433d589b68e42cd0
  Instance Type:             t3.large
  Provider ID:               aws:///ap-northeast-2c/i-0433d589b68e42cd0
  Ssh Key Name:              capi-seungkyu

 

Control plane Provider Controller

Control plane provider controller 는 Kubernetes control plane 에 대한 정보를 관리하는 Controller 이다.

  • kubeadmcontrolplanes

kubeadmcontrolplanes 은 말그래도 control plane 의 정보를 담고 있으며 앞에서 살펴본 정보들을 활용한다.

  • Infrastructure Template 으로 AWSMachineTemplate 을 사용
  • Kubeadm Config 로 Init Configuration 과 Join Configuration 정보
  • Replicas 개수 정보
  • Kubernetes version 정보
  Infrastructure Template:
    API Version:  infrastructure.cluster.x-k8s.io/v1alpha3
    Kind:         AWSMachineTemplate
    Name:         capi-quickstart-control-plane
    Namespace:    capi-quickstart
  Kubeadm Config Spec:
    Cluster Configuration:
      API Server:
        Extra Args:
          Cloud - Provider:  aws
      Controller Manager:
        Extra Args:
          Cloud - Provider:  aws
      Dns:
      Etcd:
      Networking:
      Scheduler:
    Init Configuration:
      Local API Endpoint:
        Advertise Address:
        Bind Port:          0
      Node Registration:
        Kubelet Extra Args:
          Cloud - Provider:  aws
        Name:                {{ ds.meta_data.local_hostname }}
    Join Configuration:
      Discovery:
      Node Registration:
        Kubelet Extra Args:
          Cloud - Provider:  aws
        Name:                {{ ds.meta_data.local_hostname }}
  Replicas:                  3
  Rollout Strategy:
    Rolling Update:
      Max Surge:  1
    Type:         RollingUpdate
  Version:        v1.20.5

 

반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

 

https://www.youtube.com/watch?v=lIzAMFUKxBE&t=4s

반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

Argo Rollout 은 new version pods 와 old version pods 를 어떻게 구분하는 것일까? Rollout 리소스로 생성하면 ReplicaSet 의 형태인 Pod 템플릿 영역인 (spec.template) 과 동일하다. 즉 ReplicaSet 이 Selector 로 지정한 label 로 Pod 를 찾아가는 것이다.

아래 Rollout 1개와 Service 2개를 가지는 sample yaml 파일을 배포하여 동작 방법을 살펴보자.

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollout-bluegreen
spec:
  replicas: 2
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollout-bluegreen
  template:
    metadata:
      labels:
        app: rollout-bluegreen
    spec:
      containers:
      - name: rollouts-demo
        image: argoproj/rollouts-demo:blue
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
  strategy:
    blueGreen:
      activeService: rollout-bluegreen-active
      previewService: rollout-bluegreen-preview
      autoPromotionEnabled: false

---
kind: Service
apiVersion: v1
metadata:
  name: rollout-bluegreen-active
spec:
  selector:
    app: rollout-bluegreen
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080

---
kind: Service
apiVersion: v1
metadata:
  name: rollout-bluegreen-preview
spec:
  selector:
    app: rollout-bluegreen
  ports:
  - protocol: TCP
    port: 80

Rollout 은 strategy 만 제외하면 ReplicaSet 혹은 Deployment 와 동일하다. 그래서 Rollout 을 배포하면 ReplicaSet 과 Pod 는 Kubernetes Core Resource 타입으로 동일하게 생성되고 조회할 수 있다.

 

.spec.selector.matchLabels 와 .spec.template.metadata.labels 에서 지정한 label 은 동일해야 한다. 이 부분은 ReplicaSet 에서 요구하는 spec 과 같다.

 

strategy 의 blueGreen 에는 rollout-bluegreen-active 와 rollout-bluegreen-preview 라는 service 가 2개가 지정되어 있다. Service 는 DNS 명 처럼 이름으로 IP 를 매칭하는 값을 가지는 기능과 Pod 를 연결하여 load balancing 해주는 대표적인 기능이 있다. rollout-bluegreen-active Service 에는 selector 로 "app: rollout-bluegreen" 값이 지정되어 있는데 이는 Rollout Template 에서 지정된 Pod 의 labels 를 가리킨다.

 

그런데 rollout-bluegreen-active Service 와 rollout-bluegreen-preview 2개 모두 같은 selector 로 같은 Pod 를 보고 있는데 어떻게 new version 과 old version 을 구분할까?

 

배포한 후에 설정들이 어떻게 바뀌는지 살펴보자.

$ kubectl apply -f rollout-blue-green.yaml
$ kubectl argo rollouts get rollout rollout-bluegreen --watch

 

 

rollout 명이 rollout-bluegreen-[해시코드값] 으로 되어 있는데 여기서 해시코드 값이 6565b74f44 은 ReplicaSet 의 해시코드와 동일하다.

$ kubectl get rs rollout-bluegreen

 

 

이 해시코드 값은 Service 에서도 찾아 볼 수 있다.

$ kubectl get svc rollout-bluegreen-active -o yaml
...
apiVersion: v1
kind: Service
metadata:
  name: rollout-bluegreen-active
  namespace: default
spec:
  clusterIP: 10.233.59.133
  clusterIPs:
  - 10.233.59.133
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: rollout-bluegreen
    rollouts-pod-template-hash: 6565b74f44
  sessionAffinity: None
  type: ClusterIP
...

selector 의 label 에 자동으로 rollouts-pod-template-hash: 6565b74f44 이 추가되어 rollout-bluegreen-active Service 는 현재 version 의 rollout 으로 생성된 Pod 를 가리키는 것을 알 수 있다.

이제 이미지를 업데이트 하여 upgrade 배포를 해보자.

$ kubectl argo rollouts set image rollout-bluegreen rollouts-demo=argoproj/rollouts-demo:yellow
$ kubectl argo rollouts get rollout rollout-bluegreen --watch

 

 

revision:2 아래에 rollout-bluegreen-6b5dc99488 이라는 새로운 해시코드 값으로 ReplicaSet 이 생성되었다.

rollout-bluegreen-preview Service 의 selector 로 지정된 labels 을 보면 rollouts-pod-template-hash:6b5dc99488 가 생성되어 new version 의 pod 를 가리키는 것을 알 수 있다.

$ kubectl get svc rollout-bluegreen-preview -o yaml
...
apiVersion: v1
kind: Service
metadata:
  name: rollout-bluegreen-preview
  namespace: default
spec:
  clusterIP: 10.233.44.227
  clusterIPs:
  - 10.233.44.227
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: rollout-bluegreen
    rollouts-pod-template-hash: 6b5dc99488
  sessionAffinity: None
  type: ClusterIP
...

배포를 완료하고 ReplicaSet 을 조회해 보면 이전 해시코드 값을 갖는 ReplicaSet 의 DESIRED, CURRENT, READY 가 0 으로 세팅되어 있는 것을 알 수 있다.

$ kubectl argo rollouts promote rollout-bluegreen
$ kubectl get rs -l app=rollout-bluegreen
NAME                           DESIRED   CURRENT   READY   AGE
rollout-bluegreen-6565b74f44   0         0         0       5h35m
rollout-bluegreen-6b5dc99488   2         2         2       10m

이 방식을 응용하면 Deployment 로도 쉽게 배포 전략을 활용할 수 있다.

반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요