아래의 그림에서 보듯이 Canary 배포를 진행할 때 일시적으로 배포를 홀딩한 상태에서 new version 에 대한 배포가 성공되었는지를 Metric 으로 판단하여 안전하게 배포를 완료할 수 있다. (metric 수집과 쿼리는 Prometheus 를 포함하여 다양한 모니터링 툴을 지원한다.)
Metric 을 통해 배포의 성공 여부를 판단하여 rollback 할 것인지, 계속 진행할 것인지를 결정할 수 있다는 것이 중요한 키 포인트다. 왜냐하면 쿠버네티스의 롤링 업데이트는 readiness 로 배포 성공여부를 판단하는데 이는 Metric 보다 판단하기에 부족할 수 있고, 중간에 에러가 발생하면 멈출 수 는 있지만 자동으로 rollback 되지는 않기 때문이다.
# kubectl argo rollouts get rollout rollouts-demo --watch
Basic rollout Canary 업그레이드
# kubectl argo rollouts set image rollouts-demo rollouts-demo=argoproj/rollouts-demo:yellow
rollout 전략이 처음 step 은 20% 만 변경하는 것이기 때문에 5개의 replica 중에 1개만 old version(blue) 이고 4 개는 new version(yellow) 이다. 그리고 현재는 pause (일시멈춤) 상태로 더이상 rollout 배포가 진행되지 않고 있다.
Basic rollout Canary 업그레이드 계속 진행
# kubectl argo rollouts promote rollouts-demo
promote 로 계속 rollout 업그레이드를 진행하면 점차 new version 이 revision:2 영역인 canary 로 replica 개수를 증가시키는 것을 볼 수 있다. duration 을 10으로 주었기 때문에 10 초 단위로 20% 씩 자동으로 올려준다.
일정 시간이 다 지나면 전체가 전부 다 업그레이드 된다.
argo rollout 은 metric 으로 정의한 성공 여부에 따라 배포를 계속 진행할 것인지 아니면 abort 시킬 것인지를 자동으로 결정할 수 있다.
Kubernetes 기반의 monitoring 을 구현한다면 최우선 순위로 검토되는 오픈소스는 단연 Prometheus 이다. 최근에는 Kubernetes 위에 실행되는 Pod와 같은 자원을 효과적으로 관리하기 위해서 Operator 를 활용하고 있는 추세인데 이에 맞춰 Prometheus 에서도 Operator 를 지원하고 있다. 일반적으로 Operator 는 중심이 되는 프로그램, 즉 Pod 와 같이 CustomResource를 정의하면 자동으로 해당 프로그램을 띄우는 방식으로 사용된다. 하지만 Prometheus Operator 는 Prometheus 자체를 띄우고 관리하는 방식이 아니라 Prometheus 에서 사용되는 설정 등을 관리하기 위해서 사용된다. 한마디로 말하면 Prometheus Operator와 Prometheus 를 모두 설치하고, Operator 로 Prometheus 설정을 CustomResource 로 관리하는 방식이다.
그럼 이제 Prometheus Operator 와 Prometheus 를 설치해 보자. 둘다 모두 prometheus-community 에서 제공하는 helm chart 를 helm3 를 이용하여 설치한다.
prometheus namespace 에 label을 name=prometheus 로 준 이유는 나중에 CustomeResource 인 Service Monitor 가 prometheus namespace 안에 생성되면 자동으로 해당 설정을 적용하기 위해서 Operator 에 알려주는 정보이다. 별도로 설정하지 않아도 prometheus namesapce 에는 자동으로 설정된다.
또 한가지 알아야 할 것은 etcd metric 을 가져오기 위해서는 endpoint 뿐만이 아니라 mTLS 인증서도 있어야 한다. kubernetes 를 kubespary 로 설치하였다면 master 노드의 해당 위치에 etcd 인증서가 있으니 이를 가지고 secret 을 미리 생성해 놓아야 metric 을 가져올 수 있다.
이제 helm 명령어로 Prometheus Operator를 설치한다. upgrade -i 옵션을 주면 설치가 안되어 있을 경우에는 create 명령과 동일하고 설치가 되어 있을 경우에는 upgrade 명령과 동일하다.
kubeEtcd 는 etcd 가 master 3대에 설치되어 있어 endpoint에 해당 ip 를 모두 넣었으며 caFile, certFile, keyFile 은 앞서 설치한 secret 명을 경로로 포함하고 있다. alertmanager 와 prometheus 모두 nodePort 로 설치하여 접근할 수 있다.
serviceMonitorNamespaceSelector 와 podMonitorNamespaceSelector 를 지정하여 Monitor 할 대상을 CustomResource 를 통해 자동으로 설정할 수 있다. 앞서 prometheus namespace 를 생성할 때 label 을 name=prometheus 로 지정한 이유가 바로 이 부분이다.
마지막으로 Prometheus 가 사용할 storage class 는 rbd 로 지정하여 자동으로 생성되게 설정한다.
Golang 으로 개발된 애플리케이션을 Docker image 로 만드는 Dockerfile 이다.
이미지 사이즈를 줄이기 위해서 빌드 이미지와 실행 이미지를 구분하여 작성하였다.
실행 이미지를 더 작게 만들고 싶으면 base를 golang 이미지 대신 alphine 이미지로 만들면 된다.
한가지 tip 이라 하면 RUN go mod vendor 를 먼저 하고 나중에 COPY . . 를 하여 소스를 복사한 부분이다.
만약 소스 복사를 먼저하면 변경되 소스로 인해서 (하위 layer 의 변경) go mod vendor 로 다운로드 하는 부분을 수행하게 된다. 하지만 go mod vendor 를 먼저한 후 소스를 복사하면 go mod vendor 에서 수정된 부분이 없으면 해당 layer 를 재사용하기 때문에 매번 다운로드를 하지 않는다.
# vi Dockerfile
FROM golang:1.16.3-stretch AS builder
LABEL AUTHOR Seungkyu Ahn (seungkyua@gmail.com)
RUN mkdir -p /build
WORKDIR /build
COPY go.mod .
COPY go.sum .
RUN go mod vendor
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/tksinfo ./cmd/server.go
RUN mkdir -p /dist
WORKDIR /dist
RUN cp /build/bin/tksinfo ./tksinfo
FROM golang:alpine3.13
RUN mkdir -p /app
WORKDIR /app
COPY --chown=0:0 --from=builder /dist /app/
EXPOSE 9111
ENTRYPOINT ["/app/tksinfo"]
CMD ["--port", "9111"]
istio 는 control plane 을 canary upgrade 가 가능하도록 revision tag 로 구분할 수 있게 권장하고 있다. 그리고 istio-operator 네임스페이스에 operator 를 설치하고, istio-system 네임스페이스에 istio control plane 을 설치한다.
operator 를 생성할 때 istio-operator 네임스페이스는 자동으로 생성되나 istio-system 네임스페이스는 operator 를 설치하기 전에 수동으로 생성해 놓아야 한다.
meshConfig 는 istiod 와 envoy proxy 가 서로 연결되는 정보를 나타내며 discoveryAddress 는 control plane 인 istiod 의 service url 을 tracing 은 Jaeger 정보를 저장할 데이터소스 url 이다. 여기서는 jaeger tracing backend 로 elastic search 를 사용하고 있다.meshConfig 의 값은 istiod 가 설치되는 istio-system 네임스페이스에 istiod-1-9-1 이라는 이름의 ConfigMap 으로 저장된다.
components 는 pilot(istiod), ingressGateway, egressGateway 에 대한 설치 정보이다. hpa 로 auto scaling 을 활용하기 위해서는 kubernetes cluster 에 metric-server 가 설치되어 있어야 한다.
아래와 같이 custom resource 를 생성한다.
사전에 node-selector 로 어느 서버에 istiod 와 gateway 를 설치할 수 있는지 지정할 수 있다.
$ kubectl label node k1-node03 taco-istio=enabled
$ kubectl label node k1-node04 taco-istio=enabled
$ kubectl label node k1-node05 taco-istio=enabled
$ kubectl apply -f istio-cr-1-9-1.yaml
설치 현황은 아래 명령으로 확인할 수 있다.
$ kubectl get iop -n istio-system
-----------------------------------------------
NAME REVISION STATUS AGE
istio-controlplane 1-9-1 HEALTHY 44h
3. 서비스별 gateway 추가 설치
gateway 는 서비스별로 추가할 수 있다. 이 또한 custom resource 만 생성하면 되며 custom resource 는 아래와 같다.
$ vi istio-gateway-cr.yaml
---
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
namespace: istio-system
name: istio-gateway-sample
spec:
revision:"1-9-1"
profile: empty
values:
global:
logging:
level:"default:info"
istioNamespace: istio-system
components:
ingressGateways:
- name: istio-ingressgateway-istio-gateway-sample
namespace: istio-gateway-sample
label:
app: istio-ingressgateway-istio-gateway-sample
istio: ingressgateway-istio-gateway-sample
enabled:true
k8s:
resources:
requests:
cpu: 1000m
memory: 1024Mi
hpaSpec:
maxReplicas:10
minReplicas:2
nodeSelector:
taco-istio: enabled
service:
type: NodePort
ports:
- name: status-port
port:15021
targetPort:15021
- name: http2
port:80
targetPort:8080
nodePort:31081
- name: https
port:443
targetPort:8443
nodePort:31443
- name: tcp
port:31400
targetPort:31400
- name: tls
port:15443
targetPort:15443
profile 값은 empty 로 지정한다. empty 는 istiod 를 설치하지 않겠다는 뜻이다.
Gateway 에 label 은 중요하다. default label 값은 app=istio-ingressgateway 와 istio=ingressgateway 이다. Gateway 를 여러개 설치하면 서비스별로 VirtualService 와 연결해야 하는데 이 때 활용되는 것이 label 이다. 그러니 Gateway 별로 구별할 수 있게 label 을 서로 다르게 주는 것이 중요하다.
새로운 네임스페이스에 생성된 Gateway 에 istiod 와 연결할 url 정보를 주는 것이 중요하다. 그런데 그 정보를 갖고 있는 meshConfig 에 넣어도 새로운 네임스페이스에 istio-1-9-1 ConfigMap 이 생성되지 않는다. 그래서 istio-1-9-1 ConfigMap 은 수동으로 설치해 주어야 한다.
Gateway 를 설치할 네임스페이스 생성과 그 네임스페이스에 istio-system 에 있는 istio-1-9-1 ConfigMap 을 복사하여 생성한다.
$ kubectl create ns istio-gateway-sample
$ kubectl get cm istio-1-9-1-n istio-system -o yaml \
istio-egressgateway-66cb5c46c7-c9q4b.istio-system SYNCED SYNCED SYNCED NOT SENT istiod-1-9-1-58ff56d6f8-dftbh 1.10-alpha.f88f93ff2b8172b37c83d6066363d76b4477bd2d
istio-egressgateway-66cb5c46c7-qjrwj.istio-system SYNCED SYNCED SYNCED NOT SENT istiod-1-9-1-58ff56d6f8-25z28 1.10-alpha.f88f93ff2b8172b37c83d6066363d76b4477bd2d
Istio 를 Operator 를 사용해서 revision 값을 주면 sidecar 를 자동으로 injection 하기 위해서 더이상 istio-injection=enabled 와 같은 Label을 쓰지 않는다. 위에 처럼 label 을 변경줘야 한다. istio-injection=enabled 으로 설명하는 문서들이 있으면 demo 와 같은 simple 한 설치이거나 이전 설치 방법이다.
간단히 설명하면 A, B, C 라는 서비스가 각각 D 라는 하나의 서비스를 호출한다고 가정하자. 이 때 D 서비스의 처리량 이상을 A 서비스가 호출한다면, B, C 서비스도 더이상 D 서비스를 호출하여 응답을 받을 수 없다. 그래서 A 서비스가 D를 호출하는 것을 buffer 에 저장하여 잠시 늦추는 효과를 준다면 D 서비스는 이상없이 동작하게 되고 B, C 서비스도 정상적인 응답을 받을 수 있다.
여기서 A서비스에 적용한 것이 Backpressure 이다.
Backpressure 를 적용하기 위한 전략은 다음과 같다.
Controlthe producer (slow down/speed up is decided by consumer)
Buffer(accumulate incoming data spikes temporarily)