블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

Istio Traffic management

조대협 (http://bcho.tistory.com)


Istio의 기능중의 하나가, 들어오는 트래픽을 여러 서비스로 라우팅을 하거나, 또는 트래픽에 테스트를 위해서 인위적으로 장애를 만들어 내는 것과 같은 트래픽 관리 기능이 있다. 

이러한 기능을 사용하려면, Istio에서 트래픽 관리에 사용되는 컴포넌트들의 컨셉을 알아야 한다. 초기에 Kubernetes의 트래픽 관리 기능인 Service, Ingress와 개념이 헷갈렸는데,  잘 정리해놓은 문서가 있어서 개념을 잘 정리할 수 있었다. 

Istio 트래픽 관리 컴포넌트는 크게 Gateway, VirtualService, DestinationRule 3가지로 정의된다.

Gateway

Gateway는 외부로부터 트래픽을 받는 최앞단으로, 트래픽을 받을 호스트명과 포트, 프로토콜을 정의한다. 웹서버에서 버추얼 호스트를 정의하는 것과 같이 정의 된다.  

아래는 Gateway의 예제이다. 


apiVersion: networking.istio.io/v1alpha3

kind: Gateway

metadata:

  name: ext-host-gwy

spec:

  selector:

    app: my-gateway-controller

  servers:

  - port:

      number: 443

      name: https

      protocol: HTTPS

    hosts:

    - ext-host.example.com

    tls:

      mode: SIMPLE

      serverCertificate: /tmp/tls.crt

      privateKey: /tmp/tls.key



이 설정은 ext-host.example.com 로 HTTPS 프로토콜로 443 포트를 통해서 트래픽을 받겠다고 정의한것이다. 

Virtual Service

들어온 트래픽을 서비스로 라우팅 하는 기능이다. 쿠버네티스의 Service가 목적지가 되는 것이고, 실제로 Virtual Service의 기능은 URI 기반으로 라우팅을 하는 Ingress와 유사하다고 보면 된다.


클라이언트가 쿠버네티스의 Service를 호출할때 라우팅 규칙에 따라서 적절한 쿠버네티스 Service로 라우팅 해주는 기능을 한다. 

아래 예제를 보자


apiVersion: networking.istio.io/v1alpha3

kind: VirtualService

metadata:

  name: bookinfo

spec:

  hosts:

  - "*"

  gateways:

  - bookinfo-gateway

  http:

  - match:

    - uri:

        exact: /productpage

    - uri:

        prefix: /static

    - uri:

        exact: /login

    - uri:

        exact: /logout

    - uri:

        prefix: /api/v1/products

    route:

    - destination:

        host: productpage

        port:

          number: 9080


예제 출처: https://github.com/istio/istio/blob/master/samples/bookinfo/networking/bookinfo-gateway.yaml

hosts는 클라이언트가 요청한 request에 대해서 어떤 host를 호출한것을 처리할것인지를 결정한다. 여기서는 hosts: “*”로 하였기 때문에, 모든 서비스 호출에 대해서 이 Virtual Host가 라우팅 처리를 하는데, 모든 서비스 호출은 gateways: bookinfo-gateway를 이용하여, 어느 gateway에서 들어온 요청을 처리하는지 지정할 수 있다. 

즉 이 VirtualService는 bookinfo-gateway 를 통해서 들어온 모든 요청에 대해서 처리하게 된다. 


그 다음 http: 부분에서 URL 별로 라우팅을 하도록 정의하였다.

http: match: 부분을 보면 /product, /login,/logout은 exact로 정의가 되어 있기 때문에 이 URL로 매칭 되는 요청이나 또는 /static, /api/v1/products는 prefix로 정의되어 있기 때문에, /static, /api/v1/products URI로 시작하는 요청에 대해서 라우팅을 처리한다.


라우팅 Destination은 route: destination에 정의하는데 productpage 쿠버네티스 Service의 9080, 즉 products.{namespace}.svc.cluster.local 서비스로 포워딩이 된다. 


앞의 예제는 gateway를 통해서 들어온 트래픽을 처리하는 방법이었다. 다른 예제를 보자. 아래 Virtual Service는 gateway와 연동되어 있지 않고 reviews.prod.svc.cluster.local 서비스를 호출하는 요청에 대한 라우팅 규칙을 정의하였다.


apiVersion: networking.istio.io/v1alpha3

kind: VirtualService

metadata:

  name: reviews-route

spec:

  hosts:

  - reviews.prod.svc.cluster.local

  http:

  - name: "reviews-v2-routes"

    match:

    - uri:

        prefix: "/wpcatalog"

    - uri:

        prefix: "/consumercatalog"

    rewrite:

      uri: "/newcatalog"

    route:

    - destination:

        host: reviews.prod.svc.cluster.local

        subset: v2

  - name: "reviews-v1-route"

    route:

    - destination:

        host: reviews.prod.svc.cluster.local

        subset: v1

예제 출처 :https://istio.io/docs/reference/config/networking/virtual-service/ 


reviews.prod.svc.cluster.local을 호출하였을때 URI가 /wpcatalog 나 /consumercatalog로 시작한다면 이를 /newcatalog로 바꾼 후에, reviews.prod.svc.cluster.local 쿠버네티스 서비스를 호출하는데, subset이 v2로 되어 있기 때문에, reviews.prod.svc.cluster.local pod 중에 v2 subset으로만 라우팅 한다.  (subset이라는 부분이 있는데, 이 부분은 추후에 설명한다.)

만약 이 URI와 매칭되는 것이 없다면, 

  - name: "reviews-v1-route"

    route:

    - destination:

        host: reviews.prod.svc.cluster.local

        subset: v1

에 의해서 다른 모든 트래픽은 reviews.prod.svc.cluster.local pod 중에 v1 subset으로만 라우팅 된다.


다른 예제를 하나 더 보자

apiVersion: networking.istio.io/v1alpha3

kind: VirtualService

metadata:

  name: my-productpage-rule

  namespace: istio-system

spec:

  hosts:

  - productpage.prod.svc.cluster.local # ignores rule namespace

  http:

  - timeout: 5s

    route:

    - destination:

        host: productpage.prod.svc.cluster.local

예제 출처 : https://istio.io/docs/reference/config/networking/virtual-service/


이 예제는 productpage.prod.svc.cluster.local 쿠버네티스 서비스를 호출 하는 Virtual Service 인데, 별도의 라우팅 없이 productpage.prod.svc.cluster.local 를 그대로 호출하도록 정의하였는데, timeout: 5s 로 줘서, 해당 서비스가 5초 동안 응답이 없으면 timeout 처리를 하도록 하였다. 


Virtual Service의 개념을 이해할때 자칫 헷갈릴 수 있는데, 아래 두가지를 기억하면 이해에 도움이 된다. 

  • 쿠버네티스의 Ingress 처럼 들어오는 트래픽에 대해서 URI 나 Header 등 요청에 따른 라우팅을 한다

  • Virtual Service는 Gateway를 통해서 들어오는 트래픽을 받아서 Service로 라우팅 시킬 수 도 있고 또는 쿠버네티스 Service로 가는 요청에 대해서 라우팅을 할 수 도 있다. 

DestinationRule

Virtual Service가 쿠버네티스 Service로 트래픽을 보내도록 라우팅을 했다면, 그 다음 Destination Rule은 그 서비스로 어떻게 트래픽을 보낼것인가를 정의한다. (하나의 Detination Rule은 하나의 Service에 대해서만 정의한다)


Virtual Service가 라우팅을 해서 Service로 트래픽을 보내면, 실제로는 그 안에 있는 Pod 들에 라우팅이 되는데, 어느 Pod 들로 트래픽을 보낼지, 그리고, 어떻게 보낼지를 정의한다. 


아래 예제는 my-svc.{namespace}.svc.cluster.local 서비스로 보내는 트래픽을 어떻게 보낼지를 정의하였는데, my-svc.{namespace}.svc.cluster.local  를 version이라는 pod 의 label에 따라서 v1,v2,v3 로 그룹핑을 하였다. 이를 subset이라고 한다. 


apiVersion: networking.istio.io/v1alpha3

kind: DestinationRule

metadata:

  name: my-destination-rule

spec:

  host: my-svc

  trafficPolicy:

    loadBalancer:

      simple: RANDOM

  subsets:

  - name: v1

    labels:

      version: v1

  - name: v2

    labels:

      version: v2

    trafficPolicy:

      loadBalancer:

        simple: ROUND_ROBIN

  - name: v3

    labels:

      version: v3

예제 출처 : https://istio.io/docs/concepts/traffic-management/#destination-rules


그 후에 pod 간 로드 밸런싱 정책은 

  host: my-svc

  trafficPolicy:

    loadBalancer:

      simple: RANDOM

와 같이 디폴트는 RANDOM 로드 밸런싱을 정의하였지만 subset v2의 경우에는

  - name: v2

    labels:

      version: v2

    trafficPolicy:

      loadBalancer:

        simple: ROUND_ROBIN


와 같이 ROUND_ROBIN을 사용하도록 하였다. 

Destination Rule을 사용하면, 서비스로 트래픽을 보내더라도, pod의 label에 따라서 서비스내의 pod들에 골라서 트래픽을 보낼 수 있고, pod간의 로드밸런싱이나 여러 정책을 정의하는 것이 가능하다.


대략적으로 이 개념들을 정리해서 하나의 그림에 그려보면 다음과 같다



읽어볼만 한글


참고할 사항

Ingress-gateway라는 용어가 종종 나오는데, 이는 Kubernetes에서 Gateway 를 구현하기 위한 실제 Pod이다. Service + Deployment로 되어 있고, 스케일을 위해서 HPA를 적용할 수 있다.


Istio를 사용한다고 해서 외부에서 들어오는 트래픽을 반드시 Istio gateway를 사용해야 하는 것은 아니다. 기존의 Kubernetes Ingress를 그대로 사용할 수 도 있고, Kong 과 같은 API gateway를 사용하는 것도 가능하다.


다른 Service간의 통신은 아래 그림과 같이 pod → 다른 Service의 Pod를 호출할때, 중간에 Load balancer를 거치거나 하지 않고 아래 그림과 같이 Envoy Proxy를 통해서 바로 이루어진다. 생각해보니, ClusterIP는 원래 Load balancer를 거치지 않았어..


그림 원본 https://medium.com/@zhaohuabing/which-one-is-the-right-choice-for-the-ingress-gateway-of-your-service-mesh-21a280d4a29c


예제 코드 참고 https://istio.io/docs/concepts/traffic-management/


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. bae 2020.02.13 17:47  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 훌륭한 블로그 항상 잘 참조하고 있습니다.
    https://istio.io/docs/reference/config/installation-options/#mixer-options의
    mixer.policy.autoscaleEnabled option을 보았을 때 mixer의 autoscale이 가능한 것 같은데
    autoscale의 기준이나 기준을 설정하는 방법에 대해 여쭤보고 싶습니다.

오픈소스 API 게이트웨이 Kong

#3 쿠버네티스 Kong

조대협 (http://bcho.tistory.com)


Kong Kubernetes

API 게이트웨이가 마이크로서비스의 중요 컴포넌트이다 보니, Kong이 마이크로 서비스에 적합한 K8S (aka. 쿠버네티스)에 어떻게 적용될 수 있는지가 궁금해서 아키텍쳐 관련 설명 내용을 찾아보았다. https://konghq.com/blog/kong-kubernetes-ingress-controller/ (아래 그림은, 이 동영상에서 캡춰하여 사용하였다.) 에 보면 Kong summit에서 발표된 영상이 있는데, 정리가 매우 잘되어 있다. 


기본 컨셉을 먼저 요약해보자면, Kong의 리소스들은 K8S의 리소스로 등록되어 사용되게 된다. API 게이트 웨이는 Ingress로 등록되고, Plugin들은 CRD (Kubernetes Custom Resource Definition)으로 등록되어 적용된다. 즉 별도의 Kong 설정을 하는 것이 아니라 쿠버네티스 리소스로 쿠버네티스 아키텍쳐에 따라서 등록이 된다는 이야기이다. 별도의 서비스나 서버를 기동하는 것이 아니라 K8S의 일부로 사용할 수 있도록 하고 있다. (동영상에서도 나오지만 Kong이 K8S 1’st citizen임을 계속 강조하고 있다.)


Kong의 쿠버네티스내의 배포 아키텍쳐는 다음과 같다. 

먼저 관리 서버 부분인 Control plane과, API 게이트 웨이 부분인 Data plane으로 나뉘어 진다. 


Control plane에는 Ingress controller가 있고, 이는 K8S의 API 서버와 통신을 통해서 명령을 받는다. 그리고 이 설정 정보를 psql 데이타 베이스에 저장한다. 

저장된 설정 정보는 Data plane 즉, API 게이트웨이 인스턴스에 반영이 된다. 

(만약에 psql이 죽어도 dataplane에 있는 프록시는 설정을 저장하고 있기 때문에 문제 없다.)


Kong의 리소스 맵핑

그러면 Kong API 게이트웨이에서 사용하는 리소스들은 어떻게 쿠버네티스 리소스로 맵핑이 될가?

쿠버네티스의 리소스를 Kong의 리소스로 맵핑(Translate) 해서 서비스 한다. 

아래는 Kong에서 사용하는 리소스들이다. 


이를 쿠버네티스 리소스로 맵핑한것은 다음과 같다. 




  • Route는 Ingress rule로 맵핑이 되고

  • Service 는 쿠버네티스의 Service로 맵핑 된다.

  • Target은 쿠버네티스의 pod가 되고

  • Upstream은 healthcheck와 LB로 정의 된다. 


게이트웨이에서 사용되는 플러그인 (라우팅이나 RateLimiting, Logging, 인증등)은 K8S의 CRD로 정의되어 있다.  CRD를 사용하게 되면 Ingress 에 반영되어서 사용된다.  

아래는 Rate limiting 플러그인을 K8S에서 CRD로 정의해서 사용한 예제이다

플러그인 CRD들은 ingress에 붙어서 사용된다. 




Ingress는 CRD와 함께 Limit rating, Logging, Routing등을 제공한다. 이러한 기능들은 대부분 Istio와 같은 Service mesh에도 있는데, Istio와 같은 서비스 메쉬와 비교할 경우, JWT 등  인증이 안되는 부분이 있는데, 이 부분을 보면 다소 차이가 있다. 


결론적으로 정리를 해보자면, Kong API 게이트웨이는 K8S에서는 Ingress로 정의되고, 플러그인은 CRD로 제공된다. Control Plane을 통해서 설정 정보가 저장되는 형태이다. 


쿠버네티스에 Kong을 설치하는 가이드는  https://docs.konghq.com/install/kubernetes/ 있는데, 설치는 Helm이나 Kustomize로 도 가능한데, 데이타 베이스에 대한 내용을 보면, 다음과 같다. 


"We recommend running Kong in the in-memory (also known as DB-less) mode inside Kubernetes as all the configuration is stored in Kubernetes control-plane. This setup drastically simplifies Kong’s operations as one doesn’t need to worry about Database provisioning, backup, availability, security, etc."


별도의 데이타 베이스 설치는 권장하지 않고, DB-less 모드로 설정하기를 권장하고 있다. 

YAML 파일로 설정을 정의해서 변경할 경우, 설정이 어떻게 전체 게이트웨이로 전파가되는지 등은 실제 테스트를 해봐야 알 수 있을듯



본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요


쿠버네티스 #17

보안 2/4 - 네트워크 정책

조대협 (http://bcho.tistory.com)

네트워크 정책 (Network Policy)

쿠버네티스의 보안 기능중의 하나가 네트워크 정책을 정의함으로써 Pod로 부터 들어오거나 나가는 트래픽을 통제할 수 있다. Network Policy라는 기능인데, 일종의 Pod용 방화벽정도의 개념으로 이해하면 된다.

특정 IP나 포트로 부터만 트래픽이 들어오게 하거나 반대로, 특정 IP나 포트로만 트래픽을 내보내게할 수 있는 등의 설정이 가능한데, 이 외에도 다음과 같은 방법으로 Pod에 대한 Network Policy를 설정할 수 있다.

Ingress 트래픽 컨트롤 정의

어디서 들어오는 트래픽을 허용할것인지를 정의하는 방법은 여러가지가 있다.

  • ipBlock
    CIDR IP 대역으로, 특정 IP 대역에서만 트래픽이 들어오도록 지정할 수 있다.

  • podSelector
    label을 이용하여, 특정 label을 가지고 있는 Pod들에서 들어오는 트래픽만 받을 수 있다. 예를 들어 DB Pod의 경우에는 apiserver 로 부터 들어오는 트래픽만 받는것과 같은 정책 정의가 가능하다.

  • namespaceSelector
    재미있는 기능중 하나인데, 특정 namespace로 부터 들어오는 트래픽만을 받을 수 있다. 운영 로깅 서버의 경우에는 운영 환경 namespace에서만 들어오는 트래픽을 받거나, 특정 서비스 컴포넌트의 namespace에서의 트래픽만 들어오게 컨트롤이 가능하다. 내부적으로 새로운 서비스 컴포넌트를 오픈했을때, 베타 서비스를 위해서 특정 서비스나 팀에게만 서비스를 오픈하고자 할때 유용하게 사용할 수 있다.

  • Protocol & Port
    받을 수 있는 프로토콜과 허용되는 포트를 정의할 수 있다.

Egress 트래픽 컨트롤 정의

Egress 트래픽 컨트롤은 ipBlock과 Protocol & Port 두가지만을 지원한다.

  • ipBlock
    트래픽이 나갈 수 있는 IP 대역을 정의한다. 지정된 IP 대역으로만 outbound 호출을할 수 있다.

  • Protocol & Port
    트래픽을 내보낼 수 있는 프로토콜과, 포트를 정의한다.

예제

예제를 살펴보자. 아래 네트워크 정책은 app:apiserver 라는 라벨을 가지고 있는 Pod들의 ingress 네트워크 정책을 정의하는 설정파일로, 5000번 포트만을 통해서 트래픽을 받을 수 있으며, role:monitoring이라는 라벨을 가지고 있는 Pod에서 들어오는 트래픽만 허용한다.


kind: NetworkPolicy

apiVersion: networking.k8s.io/v1

metadata:

 name: api-allow-5000

spec:

 podSelector:

   matchLabels:

     app: apiserver

 ingress:

 - ports:

   - port: 5000

   from:

   - podSelector:

       matchLabels:

         role: monitoring




네트워크 정책을 정의하기 위한 전체 스키마는 다음과 같다.

apiVersion: networking.k8s.io/v1

kind: NetworkPolicy

metadata:

 name: test-network-policy

 namespace: default

spec:

 podSelector:

   matchLabels:

     role: db

 policyTypes:

 - Ingress

 - Egress

 ingress:

 - from:

   - ipBlock:

       cidr: 172.17.0.0/16

       except:

       - 172.17.1.0/24

   - namespaceSelector:

       matchLabels:

         project: myproject

   - podSelector:

       matchLabels:

         role: frontend

   ports:

   - protocol: TCP

     port: 6379

 egress:

 - to:

   - ipBlock:

       cidr: 10.0.0.0/24

   ports:

   - protocol: TCP

     port: 5978


자 그럼, 간단하게 네트워크 정책을 정의해서 적용하는 테스트를 해보자

app:shell 이라는 라벨을 가지는 pod와 app:apiserver 라는 라벨을 가지는 pod 를 만든후에, app:shell pod에서 app:apiserver pod로 HTTP 호출을 하는 것을 테스트 한다.

다음 app:apiserver pod에 label이 app:loadbalancer 인 Pod만 호출을 받을 수 있도록 네트워크 정책을 적용한 후에, app:shell pod에서 app:apiserver로 호출이 되지 않는 것을 확인해보도록 하겠다.


테스트 환경은 구글 클라우드 쿠버네티스 엔진 ( GKE : Google cloud Kubernetes engine) 를 사용하였다.

GKE의 경우에는 NetworkPolicy가 Default로 Disable 상태이기 때문에, GKE 클러스터를 만들때 또는 만든 후에, 이 기능을 Enabled 로 활성화 해줘야 한다.

아래는 GKE 클러스터 생성시, 이 기능을 활성화 하는 부분이다.


클러스터 설정이 끝났으면, 이제 테스트에 사용할 Pod 를 준비해보자.

apiserver는 아래와 같이 server.js 의 node.js 파일을 가지고 8080 포트를 통해서 서비스하는 pod가 된다.

var os = require('os');


var http = require('http');

var handleRequest = function(request, response) {

 response.writeHead(200);

 response.end("Hello World! I'm API Server  "+os.hostname() +" \n");


 //log

 console.log("["+

Date(Date.now()).toLocaleString()+

"] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);

이 서버로 컨테이너 이미지를 만들어서 등록한후에, 그 이미지로 아래와 같이 app:apiserver 라벨을 가지는

Pod를 생성해보자.


apiVersion: v1

kind: Pod

metadata:

 name: apiserver

 labels:

   app: apiserver

spec:

 containers:

 - name: apiserver

   image: gcr.io/terrycho-sandbox/apiserver:v1

   ports:

   - containerPort: 8080

마찬가지로, app:shell 라벨을 가진 Pod도 같은  server.js 파일로 생성한다.

app:apiserver와 app:shell 라벨을 가진 pod를 생성하기 위한 코드와 yaml 파일은 https://github.com/bwcho75/kube101/tree/master/10.security/3.%20networkpolicy 를 참고하기 바란다.


두 개의 Pod를 생성하였으면 shell pod 에 kubectl exec -it {shell pod 명} -- /bin/bash를 이용해서 로그인한후에

apiserver의 URL인 10.20.3.4:8080으로 curl 로 요청을 보내보면 아래와 같이 호출되는 것을 확인할 수 있다.



이번에는 네트워크 정책을 정의하여, app:apiserver pod에 대해서 app:secure-shell 라벨을 가진 pod로 부터만 접근이 가능하도록 정책을 정해서 정의해보자


아래는 네트워크 정책을 정의한 accept-secureshell.yaml 파일이다.

kind: NetworkPolicy

apiVersion: networking.k8s.io/v1

metadata:

 name: accept-secureshell

spec:

 policyTypes:

 - Ingress

 podSelector:

   matchLabels:

     app: apiserver

 ingress:

 - from:

   - podSelector:

       matchLabels:

         app: secureshell


이 설덩은 app:apiserver 라벨이 설정된 Pod로의 트래픽은 라벨이 app:secureshell에서 보내는 트래픽만 받도록 설정한 정책이다.

%kubectl create -f accept-secureshell.yaml

명령어를 이용해서 앞에서 만든 정책을 적용한후에, 앞에서와 같이 app:shell → app:apiserver로 curl 호출을 실행하면 다음과 같이 연결이 막히는 것을 확인할 수 있다.



이외에도 다양한 정책으로, 트래픽을 컨트롤할 수 있는데, 이에 대한 레시피는 https://github.com/ahmetb/kubernetes-network-policy-recipes 문서를 참고하면 좋다.

본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. ㅇㅇ 2018.10.15 17:36  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 좋은 정보 많이 얻어가고있습니다. 다름아니라 질문있어 남깁니다.ㅜㅜ
    지금 제가 포드와 포드끼리 ssh를 통해 연결하려고 하는대 22번포트 연결거부로 안됩니다.
    네트워크정책으로 allow-all해보기도하고 host os 포트를 바꿔보기도하고 했는대 잘되지않습니다. 혹시 어떻게하면 연결이되는지 알수 있을까요?


쿠버네티스 #9

Health Check


조대협 (http://bcho.tistory.com)


쿠버네티스는 각 컨테이너의 상태를 주기적으로 체크해서, 문제가 있는 컨테이너를 자동으로 재시작하거나 또는 문제가 있는 컨테이너(Pod를) 서비스에서 제외할 수 있다. 이러한 기능을 헬쓰 체크라고 하는데, 크게 두가지 방법이 있다.

컨테이너가 살아 있는지 아닌지를 체크하는 방법이 Liveness probe 그리고 컨테이너가 서비스가 가능한 상태인지를 체크하는 방법을 Readiness probe 라고 한다.


Probe types

Liveness probe와 readiness probe는 컨테이너가 정상적인지 아닌지를 체크하는 방법으로 다음과 같이 3가지 방식을 제공한다.

  • Command probe

  • HTTP probe

  • TCP probe


그럼 각각에 대해서 살펴보자

Command probe

Command probe는 컨테이너의 상태 체크를 쉘 명령을 수행하고 나서, 그 결과를 가지고 컨테이너의 정상여부를 체크한다. 쉘 명령어를 수행한 후, 결과값이 0 이면 성공, 0이 아니면 실패로 간주한다.

아래는 command probe 를 사용한 예이다.

apiVersion: v1

kind: Pod

metadata:

 name: liveness-pod

spec:

 containers:

 - name: liveness

   image: gcr.io/terrycho-sandbox/liveness:v1

   imagePullPolicy: Always

   ports:

   - containerPort: 8080

   livenessProbe:

     exec:

       command:

       - cat

       - /tmp/healthy


Readiness probe 또는 liveness probe 부분에 exec: 으로 정의하고, command: 아래에 실행하고자 하는 쉘 명령어에 대한 인자를 기술한다.

이 쉘명령이 성공적으로 실행되서 0을 리턴하면, probe를 정상으로 판단한다.


HTTP probe

가장 많이 사용하는 probe 방식으로 HTTP GET을 이용하여, 컨테이너의 상태를 체크한다.

지정된 URL로 HTTP GET 요청을 보내서 리턴되는 HTTP 응답 코드가 200~300 사이면 probe를 정상으로 판단하고, 그 이외의 값일 경우에는 비정상으로 판단한다.

아래는 HTTP probe를 이용한 readiness probe를 정의한 예제이다.


metadata:

 name: readiness-rc

spec:

 replicas: 2

 selector:

   app: readiness

 template:

   metadata:

     name: readiness-pod

     labels:

       app: readiness

   spec:

     containers:

     - name: readiness

       image: gcr.io/terrycho-sandbox/readiness:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080

       readinessProbe:

         httpGet:

           path: /readiness

           port: 8080


liveness 또는 readinessProbe  항목 아래에 httpGet이라는 이름으로 정의하고, path에  HTTP GET을 보낼 URL을 그리고, port에는 HTTP GET을 보낼 port 를 지정한다.

일반적인 HTTP 서비스를 보내는 port와 HTTP readiness를 서비스 하는 포트를 분리할 수 있는데, HTTP GET 포트가 외부에 노출될 경우에는 DDos 공격등을 받을 수 있는 가능성이 있기 때문에, 필요하다면 서비스 포트와 probe 포트를 분리해서 구성할 수 있다.

TCP probe

마지막으로 TCP probe는 지정된 포트에 TCP 연결을 시도하여, 연결이 성공하면, 컨테이너가 정상인것으로 판단한다. 다음은 tcp probe를 적용한 liveness probe의 예제이다.


apiVersion: v1

kind: Pod

metadata:

 name: liveness-pod-tcp

spec:

 containers:

 - name: liveness

   image: gcr.io/terrycho-sandbox/liveness:v1

   imagePullPolicy: Always

   ports:

   - containerPort: 8080

   livenessProbe:

     tcpSocket:

       port: 8080

     initialDelaySeconds: 5

     periodSeconds: 5


Tcp probe는 간단하게, livenessProbe나 readinessProbe 아래 tcpSocket이라는 항목으로 정의하고 그 아래 port 항목에 tcp port를 지정하면 된다. 이 포트로 TCP 연결을 시도하고, 이 연결이 성공하면 컨테이너가 정상인것으로 실패하면 비정상으로 판단한다.


그러면 실제로 Liveness Probe와 Readiness Probe를 예제를 통해서 조금 더 상세하게 살펴보도록 하자.

Liveness Probe

Liveness probe는 컨테이너의 상태를 주기적으로 체크해서, 응답이 없으면 컨테이너를 자동으로 재시작해준다. 컨테이너가 정상적으로 기동중인지를 체크하는 기능이다.


Liveness probe는 Pod의 상태를 체크하다가, Pod의 상태가 비정상인 경우 kubelet을 통해서 재 시작한다.



이해를 돕기 위해서 예제를 하나 살펴보자.

node.js 애플리케이션을 기동하는 컨테이너를 만들어서 배포 하도록 한다. node.js는 앞에서 사용한 애플리케이션과 동일한 server.js  애플리케이션을 사용한다.

헬쓰 체크를 하는 방법은 여러가지가 있지만, 컨터이너에서 “cat /tmp/healthy” 명령어를 실행해서 성공하면 컨테이너를 정상으로 판단하고 실패하면 비정상으로 판단하도록 하겠다.

이를 위해서 컨테이너 생성시에 /tmp/ 디렉토리에 healthy 파일을 복사해 놓도록 한다.

heatlhy 파일의 내용은 아래와 같다.

i'm healthy


파일만 존재하면 되기 때문에 내용은 크게 중요하지 않다.

다음 Dockerfile을 다음과 같이 작성하자

FROM node:carbon

EXPOSE 8080

COPY server.js .

COPY healthy /tmp/

CMD node server.js > log.out


앞서 작성한 healthy 파일을 /tmp 디렉토리에 복사하였다.


이제 pod를 정의해보자 다음은 liveness-pod.yaml 파일이다.

여기에 cat /tmp/healthy 명령을 이용하여 컨테이너의 상태를 체크하도록 하였다.


apiVersion: v1

kind: Pod

metadata:

 name: liveness-pod

spec:

 containers:

 - name: liveness

   image: gcr.io/terrycho-sandbox/liveness:v1

   imagePullPolicy: Always

   ports:

   - containerPort: 8080

   livenessProbe:

     exec:

       command:

       - cat

       - /tmp/healthy

     initialDelaySeconds: 5

     periodSeconds: 5



컨테이너가 기동 된후 initialDelaySecond에 설정된 값 만큼 대기를 했다가 periodSecond 에 정해진 주기 단위로 컨테이너의 헬스 체크를 한다. initialDelaySecond를 주는 이유는, 컨테이너가 기동 되면서 애플리케이션이 기동될텐데, 설정 정보나 각종 초기화 작업이 필요하기 때문에, 컨테이너가 기동되자 마자 헬스 체크를 하게 되면, 서비스할 준비가 되지 않았기 때문에 헬스 체크에 실패할 수 있기 때문에, 준비 기간을 주는 것이다. 준비 시간이 끝나면, periodSecond에 정의된 주기에 따라 헬스 체크를 진행하게 된다.


헬스 체크 방식은 여러가지가 있는데, HTTP 를 이용하는 방식 TCP를 이용하는 방식 쉘 명령어를 이용하는 방식 3가지가 있다. 이 예제에서는 쉘 명령을 이용하는 방식을 사용하였다.

“cat /tmp/healty” 라는 명령을 사용하였고, 이 명령 실행이 성공하면 이 컨테이너를 정상이라고 판단하고, 만약 이 명령이 실패하면 컨테이너가 비정상이라고 판단한다.


앞서 작성한 Dockerfile을 이용해서 컨테이너를 생성한 후, 이 컨테이너를 리파지토리에 등록하자.

다음 앞에서 작성한 liveness-prod.yaml 파일을 이용하여 Pod를 생성해보자.




다음, 테스트를 위해서 /tmp/healthy 파일을 인위적으로 삭제해보자



파일을 삭제하면 위의 그림과 같이 cat /tmp/healthy 는 exit code 1 을 내면서 에러로 종료된다.

수초 후에, 해당 컨테이너가 재 시작되는데, kubectl get pod 명령을 이용하여 pod의 상태를 확인해보면 다음과 같다.


liveness-pod는 정상적으로 실행되고는 있지만, RESTARTS 항목을 보면 한번 리스타트가 된것을 볼 수 있다.

상세 정보를 보기 위해서 kubectl describe pod liveness-pod 명령을 실행해보면 다음과 같다.


위의 그림과 같이 중간에, “Killing container with id docker://liveness:Container failed liveness probe.. Container will be killed and recreated.” 메세지가 나오면서 liveness probe 체크가 실패하고, 컨테이너를 재 시작하는 것을 확인할 수 있다.

Readiness probe

컨테이너의 상태 체크중에 liveness의 경우에는 컨테이너가 비정상적으로 작동이 불가능한 경우도 있지만, Configuration을 로딩하거나, 많은 데이타를 로딩하거나, 외부 서비스를 호출하는 경우에는 일시적으로 서비스가 불가능한 상태가 될 수 있다. 이런 경우에는 컨테이너를 재시작한다 하더라도 정상적으로 서비스가 불가능할 수 있다. 이런 경우에는 컨테이너를 일시적으로 서비스가 불가능한 상태로 마킹해주면 되는데, 이러한 기능은 쿠버네티스의 서비스와 함께 사용하면 유용하게 이용할 수 있다.


예를 들어 쿠버네티스 서비스에서 아래와 같이 3개의 Pod를 로드밸런싱으로 서비스를 하고 있을때, Readiness probe 를 이용해서 서비스 가능 여부를 주기적으로 체크한다고 하자. 이 경우 하나의 Pod가 서비스가 불가능한 상태가 되었을때, 즉 Readiness Probe에 대해서 응답이 없거나 실패 응답을 보냈을때는 해당 Pod를 사용 불가능한 상태로 체크하고 서비스 목록에서 제외한다.



Liveness probe와 차이점은 Liveness probe는 컨테이너의 상태가 비정상이라고 판단하면, 해당 Pod를 재시작하는데 반해, Readiness probe는 컨테이너가 비정상일 경우에는 해당 Pod를 사용할 수 없음으로 표시하고, 서비스등에서 제외한다.


간단한 예제를 보자. 아래 server.js 코드는 /readiness 를 호출하면 파일 시스템내에  /tmp/healthy라는 파일이 있으면 HTTP 응답코드 200 정상을 리턴하고, 파일이 없으면 HTTP 응답코드 500 비정상을 리턴하는 코드이다.


server.js 파일

var os = require('os');

var fs = require('fs');


var http = require('http');

var handleRequest = function(request, response) {

 if(request.url == '/readiness') {

   if(fs.existsSync('/tmp/healthy')){

     // healthy

     response.writeHead(200);

     response.end("Im ready I'm  "+os.hostname() +" \n");

   }else{

     response.writeHead(500);

     response.end("Im not ready I'm  "+os.hostname() +" \n");

   }

 }else{

   response.writeHead(200);

   response.end("Hello World! I'm  "+os.hostname() +" \n");

 }


 //log

 console.log("["+

Date(Date.now()).toLocaleString()+

"] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);


다음은 replication controller를 다음과 같이 정의한다.


readiness-rc.yaml 파일

apiVersion: v1

kind: ReplicationController

metadata:

 name: readiness-rc

spec:

 replicas: 2

 selector:

   app: readiness

 template:

   metadata:

     name: readiness-pod

     labels:

       app: readiness

   spec:

     containers:

     - name: readiness

       image: gcr.io/terrycho-sandbox/readiness:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080

       readinessProbe:

         httpGet:

           path: /readiness

           port: 8080

         initialDelaySeconds: 5

         periodSeconds: 5


앞의 Liveness probe와 다르게,  이번에는 Command probe가 아니라 HTTP 로 체크를 하는 HTTP Probe를 적용해보자 HTTP Probe는 , HTTP GET으로 /readiness URL로 5초마다 호출을 해서 HTTP 응답 200을 받으면 해당 컨테이너를 정상으로 판단하고 200~300 범위를 벗어난 응답 코드를 받으면 비정상으로 판단하여, 서비스 불가능한 상태로 인식해서 쿠버네티스 서비스에서 제외한다.


Replication Controller 로 의해서 Pod들을 생성하였으면 이에 대한 로드 밸런서 역할을할 서비스를 배포한다.


readiness-svc.yaml 파일

apiVersion: v1

kind: Service

metadata:

 name: readiness-svc

spec:

 selector:

   app: readiness

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

 type: LoadBalancer


서비스가 기동되고 Pod들이 정상적으로 기동된 상태에서 kubectl get pod 명령을 이용해서 현재 Pod 리스트를 출력해보면 다음과 같다.


2개의 Pod가 기동중인것을 확인할 수 있다.


서비스가 기동중인 상태에서 인위적으로 하나의 컨테이너를 서비스 불가 상태로 만들어보자.

앞에서만든 server.js가, 컨테이너 내의 /tmp/healthy 파일의 존재 여부를 체크하기 때문에,  /tmp/healthy 파일을 삭제하면 된다.

아래와 같이

%kubectl exec  -it readiness-rc-5v64f -- rm /tmp/healthy

명령을 이용해서 readiness-rc-5v64f pod의 /tmp/healthy 파일을 삭제해보자

다음 kubectl describe pod readiness-rc-5v64f 명령을 이용해서 해당 Pod의 상태를 확인할 수 있는데, 아래 그림과 같이 HTTP probe가 500 상태 코드를 리턴받고 Readniess probe가 실패한것을 확인할 수 있다.


이 상태에서 kubectl get pod로 pod 목록을 확인해보면 다음과 같다.




Readiness probe가 실패한 readiness-rc-5v64f 의 상태가 Running이기는 하지만 Ready 상태가 0/1인것으로 해당 컨테이너가 준비 상태가 아님을 확인할 수 있다.

이 Pod들에 연결된 서비스를 여러번 호출해보면 다음과 같은 결과를 얻을 수 있다.



모든 호출이 readiness-rc-5v64f 로 가지않고, 하나 남은 정상적인 Pod인 readiness-rc-89d89 로만 가는 것을 확인할 수 있다.  


다음글에서는 Deployment에 대해서 알아보도록 하겠다.



본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. 초보 2018.08.07 15:59  댓글주소  수정/삭제  댓글쓰기

    안녕하세요? 쿠버네티스 정보를 찾다가 많은 것을 배워갑니다.
    질문이 하나 있는데요...
    liveness probe에 의해서 특정 pod(container)를 재시작 하는 동안에
    그 pod는 자동으로 서비스에서 제외되나요...?
    아니면 재시작만 해줄뿐 재시작 동안 서비스(예를 들면 rest API)를 호출하게 되면
    재시작 중인 pod로도 트래픽이 가서 error response를 받을 수 있는 것인지 궁금합니다...

  2. odark 2020.03.09 15:05  댓글주소  수정/삭제  댓글쓰기

    관리자의 승인을 기다리고 있는 댓글입니다


쿠버네티스 #8

Ingress


조대협 (http://bcho.tistory.com)



쿠버네티스의 서비스는, L4 레이어로 TCP 단에서 Pod들을 밸런싱한다.

서비스의 경우에는 TLS (SSL)이나, VirtualHost와 같이 여러 호스트명을 사용하거나 호스트명에 대한 라우팅이 불가능하고, URL Path에 따른 서비스간 라우팅이 불가능하다.

또한 마이크로 서비스 아키텍쳐 (MSA)의 경우에는 쿠버네티스의 서비스 하나가 MSA의 서비스로 표현되는 경우가 많고 서비스는 하나의 URL로 대표 되는 경우가 많다. (/users, /products, …)

그래서 MSA 서비스간의 라우팅을 하기 위해서는 API 게이트웨이를 넣는 경우가 많은데, 이 경우에는 API 게이트웨이에 대한 관리포인트가 생기기 때문에, URL 기반의 라우팅 정도라면, API 게이트웨이 처럼 무거운 아키텍쳐 컴포넌트가 아니라, L7 로드밸런서 정도로 위의 기능을 모두 제공이 가능하다.


쿠버네티스에서 HTTP(S)기반의 L7 로드밸런싱 기능을 제공하는 컴포넌트를 Ingress라고 한다.

개념을 도식화 해보면 아래와 같은데, Ingress 가 서비스 앞에서 L7 로드밸런서 역할을 하고, URL에 따라서 라우팅을 하게 된다.


Ingress 가 서비스 앞에 붙어서, URL이 /users와 /products 인것을 각각 다른 서비스로 라우팅 해주는 구조가 된다.


Ingress 은 여러가지 구현체가 존재한다.

구글 클라우드의 경우에는 글로벌 로드 밸런서(https://github.com/kubernetes/ingress-gce/blob/master/README.md) 를 Ingress로 사용이 가능하며, 오픈소스 구현체로는 nginx (https://github.com/kubernetes/ingress-nginx/blob/master/README.md)  기반의 ingress 구현체가 있다.  상용 제품으로는 F5 BIG IP Controller (http://clouddocs.f5.com/products/connectors/k8s-bigip-ctlr/v1.5/) 가 현재 사용이 가능하고, 재미있는 제품으로는 오픈소스 API 게이트웨이 솔루션인 Kong (https://konghq.com/blog/kubernetes-ingress-controller-for-kong/)이 Ingress 컨트롤러의 기능을 지원한다.

각 구현체마다 설정 방법이 다소 차이가 있으며, 특히 Ingress 기능은 베타 상태이기 때문에, 향후 변경이 있을 수 있음을 감안하여 사용하자

URL Path 기반의 라우팅

이 글에서는 구글 클라우드 플랫폼의 로드밸런서를 Ingress로 사용하는 것을 예를 들어 설명한다.

위의 그림과 같이 users 와 products 서비스 두개를 구현하여 배포하고, 이를 ingress를 이용하여 URI가  /users/* 와 /products/* 를 각각의 서비스로 라우팅 하는 방법을 구현해보도록 하겠다.


node.js와 users와 products 서비스를 구현한다.

서비스는 앞에서 계속 사용해왔던 간단한 HelloWorld 서비스를 약간 변형해서 사용하였다.


아래는 users 서비스의 server.js 코드로 “Hello World! I’m User server ..”를 HTTP 응답으로 출력하도록 하였다.  Products 서비스는 User server를 product 서버로 문자열만 변경하였다.


var os = require('os');


var http = require('http');

var handleRequest = function(request, response) {

 response.writeHead(200);

 response.end("Hello World! I'm User server "+os.hostname() +" \n");


 //log

 console.log("["+

Date(Date.now()).toLocaleString()+

"] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);


다음으로 서비스를 배포해야 하는데, Ingress를 사용하려면 서비스는 Load Balancer 타입이 아니라, NodePort 타입으로 배포해야 한다.  다음은 user 서비스를 nodeport 서비스로 배포하는 yaml 스크립트이다. (Pod를 컨트롤하는 Deployment 스크립트는 생략하였다.)


users-svc-nodeport.yaml

apiVersion: v1

kind: Service

metadata:

 name: users-node-svc

spec:

 selector:

   app: users

 type: NodePort

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

 

같은 방식으로, Product 서비스도 아래와 같이 NodePort로 배포한다.

product-svc-nodeport.yaml

apiVersion: v1

kind: Service

metadata:

 name: products-node-svc

spec:

 selector:

   app: products

 type: NodePort

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

     

이때 별도로 nodeport를 지정해주지 않았는데, 자동으로 쿠버네티스 클러스터가 nodeport를 지정해준다.

아래와 같이 products-node-svc와 users-node-svc가 각각 배포된것을 확인할 수 있고, ClusterIP의 포트는 80, NodePort는 각각 31442, 32220으로 배포된것을 확인할 수 있다.




다음 Ingress를 생성해보자. 다음은 hello-ingress 라는 이름으로 위에서 만든 두개의 서비스를 라우팅해주는 서비스를 생성하기 위한 yaml  파일이다.

hello-ingress.yaml

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

 name: hello-ingress

spec:

 rules:

 - http:

     paths:

     - path: /users/*

       backend:

         serviceName: users-node-svc

         servicePort: 80

     - path: /products/*

       backend:

         serviceName: products-node-svc

         servicePort: 80


spec 부분에, rules.http.paths 부분에, 라우팅할 path와 서비스를 정의해준다.

User 서비스는 /users/* URI인 경우 라우팅하게 하고, 앞에서 만든 users-node-svc로 라우팅하도록 한다. 이때 servicePort는 ClusterIP의 service port를 지정한다. (Google Cloud HTTP Load balancer를 이용하는 Ingress의 경우에는  실질적으로는 nodeport로 통신을 하지만 별도로 지정하지 않고 ingress가 자동으로 해당 서비스의 nodeport를 찾아서 맵핑이 된다. )
(참고 : https://kubernetes.io/docs/concepts/services-networking/ingress/

Lines 12-14: A backend is a service:port combination as described in the services doc. Ingress traffic is typically sent directly to the endpoints matching a backend.)


%kubectl create -f hello-ingress.yaml

을 실행하면 ingress가 생성이 되고 kubectl get ing 명령어를 이용하면 생성된 ingress를 확인할 수 있다.



Ingress 가 생성된 후, 실제로 사용이 가능하기까지는 약 1~2분의 시간이 소요된다. 물리적으로 HTTP 로드밸런서를 생성하고, 이 로드밸런서가 서비스가 배포되어 있는 노드에 대한 HealthCheck를 완료하고 문제가 없으면 서비스를 제공하는데, HealthCheck 주기가 1분이기 때문에, 1~2분 정도를 기다려 주는게 좋다. 그전까지는 404 에러나 500 에러가 날것이다.


준비가 끝난후, curl 명령을 이용해서 ingress의 URL에 /users/ 와 /products를 각각 호출해보면 각각, users를 서비스 하는 서버와, products를 서비스 하는 서버로 라우팅이 되서 각각 다른 메세지가 출력되는 것을 확인할 수 있다.




그러면 내부적으로 클라우드 내에서 Ingress를 위한 인프라가 어떻게 생성되었는지 확인해보자

구글 클라우드 콘솔에서 아래와 같이 Network services > Load balancing 메뉴로 들어가보자



아래와 같이 HTTP 로드밸런서가 생성이 된것을 확인할 수 있다.


이름을 보면 k8s는 쿠버네티스용 로드밸런서임을 뜻하고, 중간에 default는 네임 스페이스를 의미한다. 그리고 ingress의 이름인 hello-ingress로 생성이 되어 있다.

로드밸런서를 클릭해서 디테일을 들어가 보면 아래와 같은 정보를 확인할 수 있다.




3개의 백엔드 (인스턴스 그룹)이 맵핑되었으며, /users/*용, /products/*용 그리고, 디폴트용이 생성되었다.

모든 트래픽이 쿠버네티스 클러스터 노드로 동일하게 들어가기 때문에, Instance group의 이름을 보면 모두 동일한것을 확인할 수 있다. 단, 중간에 Named Port 부분을 보면 포트가 다른것을 볼 수 있는데, 31442, 32220 포트를 사용하고 있고, 앞에서 users, produtcs 서비스를 nodeport로 생성하였을때, 자동으로 할당된 nodeport이다.


개념적으로 다음과 같은 구조가 된다.


(편의상 디폴트 백앤드의 라우팅은 표현에서 제외하였다.)


Ingress에 접속되는 서비스를 LoadBalancer나 ClusterIP타입이 아닌 NodePort 타입을 사용하는 이유는, Ingress로 사용되는 구글 클라우드 로드밸런서에서, 각 서비스에 대한 Hearbeat 체크를 하기 위해서인데, Ingress로 배포된 구글 클라우드 로드밸런서는 각 노드에 대해서 nodeport로 Heartbeat 체크를 해서 문제 있는 노드를 로드밸런서에서 자동으로 제거나 복구가되었을때는 자동으로 추가한다.

Static IP 지정하기

서비스와 마찬가지로 Ingress 역시 Static IP를 지정할 수 있다.

서비스와 마찬가지로, static IP를 gcloud 명령을 이용해서 생성한다. 이때 IP를 regional로 생성할 수 도 있지만, ingress의 경우에는 global IP를 사용할 수 있다. --global 옵션을 주면되는데, global IP의 경우에는 regional IP와는 다르게 구글 클라우드의 망 가속 기능을 이용하기 때문에, 구글 클라우드의 100+ 의 Pop (Point of Presence)를 이용하여 가속이 된다.


조금 더 깊게 설명을 하면, 일반적으로 한국에서 미국으로 트래픽을 보낼 경우 한국 → 인터넷 → 미국 식으로 트래픽이 가는데 반해 global IP를 이용하면, 한국에서 가장 가까운 Pop (일본)으로 접속되고, Pop으로 부터는 구글 클라우드의 전용 네트워크를 이용해서 구글 데이타 센터까지 연결 (한국 → 인터넷 → 일본 Pop → 미국 ) 이 되기 때문에 일반 인터넷으로 연결하는 것 대비에서 빠른 성능을 낼 수 있다.


아래와 같이 gcloud 명령을 이용하여, global IP를 생성한다.




구글 클라우드 콘솔에서, 정적 IP를 확인해보면 아래와 같이 hello-ingress-ip 와 같이 IP가 생성되어 등록되어 있는 것을 확인할 수 있다.



Static IP를 이용해서 hello-ingress-staticip 이름으로 ingress를 만들어보자

다음과 같이 hello-ingress-staticip.yaml 파일을 생성한다.


apiVersion: extensions/v1beta1

kind: Ingress

metadata:

 name: hello-ingress-staticip

 annotations:

   kubernetes.io/ingress.global-static-ip-name: "hello-ingress-ip"

spec:

 rules:

 - http:

     paths:

     - path: /users/*

       backend:

         serviceName: users-node-svc

         servicePort: 80

     - path: /products/*

       backend:

         serviceName: products-node-svc

         servicePort: 80


이 파일을 이용하여, ingress를 생성한 후에, ingress ip를 확인하고 curl 을 이용해서 결과를 확인하면 다음과 같다.


Ingress with TLS

이번에는 Ingress 로드밸런서를 HTTP가 아닌 HTTPS로 생성해보겠다.


SSL 인증서 생성

SSL을 사용하기 위해서는 SSL 인증서를 생성해야 한다. openssl (https://www.openssl.org/)툴을 이용하여 인증서를 생성해보도록 한다.


인증서 생성에 사용할 키를 생성한다.

%openssl genrsa -out hello-ingress.key 2048

명령으로 키를 생성하면 hello-ingress.key라는 이름으로 Private Key 파일이 생성된다.




다음 SSL 인증서를 생성하기 위해서, 인증서 신청서를 생성한다.인증서 신청서 생성시에는 앞에서 생성한 Private Key를 사용한다.

다음 명령어를 실행해서 인증서 신청서 생성을 한다.

%openssl req -new -key hello-ingress.key -out hello-ingress.csr

이때 인증서 내용에 들어갈 국가, 회사 정보, 연락처등을 아래와 같이 입력한다.


인증서 신청서가 hello-ingress.csr 파일로 생성이 되었다. 그러면 이 신청서를 이용하여, SSL 인증서를 생성하자. 테스트이기 때문에 공인 인증 기관에 신청하지 않고, 간단하게 사설 인증서를 생성하도록 하겠다.


다음 명령어를 이용하여 hello-ingress.crt라는 이름으로 SSL 인증서를 생성한다.

%openssl x509 -req -day 265 -in hello-ingress.csr -signkey hello-ingress.key -out hello-ingress.crt



설정하기

SSL 인증서 생성이 완료되었으면, 이 인증서를 이용하여 SSL을 지원하는 ingress를 생성해본다.

SSL 인증을 위해서는 앞서 생성한 인증서와 Private Key 파일이 필요한데, Ingress는 이 파일을 쿠버네티스의 secret 을 이용하여 읽어드린다.


Private Key와 SSL 인증서를 저장할 secret를 생성해보자 앞에서 생성한 hello-ingress.key와 hello-ingress.crt 파일이 ./ssl_cert 디렉토리에 있다고 하자


다음과 같이 kubectl create secret tls 명령을 이용해서 hello-ingress-secret 이란 이름의 secret을 생성한다.

%kubectl create secret tls hello-ingress-serect --key ./ssl_cert/hello-ingress.key --cert ./ssl_cert/hello-ingress.crt


명령을 이용하여 secret을 생성하면, key 이라는 이름으로 hello-ingress.key 파일이 바이너리 형태로 secret에 저장되고 마찬가지로 cert라는 이름으로 hello-ingress.crt 가 저장된다.


생성된 secret을 확인하기 위해서

%kubectl describe secret hello-ingress-secret

명령을 실행해보면 아래와 같이 tls.key 와 tls.crt 항목이 각각 생성된것을 확인할 수 있다.


다음 SSL을 지원하는 ingress를 생성해야 한다.

앞에서 생성한 HTTP ingress와 설정이 다르지 않으나 spec 부분에 tls라는 항목에 SSL 인증서와 Private Key를 저장한 secret 이름을 secretName이라는 항목으로 넘겨줘야 한다.


hello-ingress-tls.yaml


apiVersion: extensions/v1beta1

kind: Ingress

metadata:

 name: hello-ingress-tls

spec:

 tls:

 - secretName: hello-ingress-secret

 rules:

 - http:

     paths:

     - path: /users/*

       backend:

         serviceName: users-node-svc

         servicePort: 80

     - path: /products/*

       backend:

         serviceName: products-node-svc

         servicePort: 80


이 파일을 이용해서 TLS ingress를 생성한 후에, IP를 조회해보자

아래와 같이 35.241.6.159 IP에 hello-ingress-tls 이름으로 ingress가 된것을 확인할 수 있고 포트는 HTTP 포트인 80 포트 이외에, HTTPS포트인 443 포트를 사용하는 것을 볼 수 있다.



다음 HTTPS로 테스트를 해보면 다음과 같이 HTTPS로 접속이 되는 것을 확인할 수 있다.



사설 인증서이기 때문에 위처럼 Not Secure라는 메세지가 뜬다. 인증서 정보를 확인해보면 아래와 같이 앞서 생성한 인증서에 대한 정보가 들어가 있는 것을 확인할 수 있다.



본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. daniem 2018.06.23 16:31  댓글주소  수정/삭제  댓글쓰기

    감사드립니다. 궁금한게 있어요.
    제가 Jenkins 컨테이너를 path 기반으로 foo.bar.com/jenkins 매핑하였는데, Jenkins는 접속 시에 /login path로 리다이렉트 하거든요. 그러다보니 foo.bar.com/jenkins로 들어가면 foo.bar.com/login으로 변경되어 버리더라구요.

    예시들은 모든 요청에 대한 응답이라 이런 내용을 알 수 없어서 여쭤봅니다.
    ingress에 /foo/* 라고 명세하였고,
    /users라는 api를 만든다면 어플리케이션은 /foo/users에 대해서 핸들링하도록 해야 되는거죠?

  2. js jang 2018.06.27 16:47  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 다시 한번 좋은 정리 감사드립니다:)
    질문이 있는데요
    "Ingress를 사용하려면 서비스는 Load Balancer 타입이 아니라, NodePort 타입으로 배포해야 한다." 라고 하셨는데요
    서비스 타입으로 Load Balancer를 사용하면 stickysession 효과를 얻을 수 있다고 알고 있는데 그럼 ingress 사용시에는 이 부분의 효과를 어떻게 얻을수 있는건가요??

    • 조대협 2018.07.10 21:42 신고  댓글주소  수정/삭제

      이건 로드밸런서에 따라 다른걸로 알고 있는데, 구글 클라우드 로드밸런서는 sticky session 기능이 있습니다. 그걸 활용하면 되고, 다른 로드 밸런서로는 nginx로 된 ingress가 있는데 sticky session이 되는지는 확실하지 않습니다. (아마 될것 같습니다.)

  3. odark 2020.03.09 12:05  댓글주소  수정/삭제  댓글쓰기

    관리자의 승인을 기다리고 있는 댓글입니다