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


Archive»


 
 

Istio #4 - 설치 및 BookInfo 예제

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

Istio 설치

그러면 직접 Istio 를 설치해보자, 설치 환경은 구글 클라우드의 쿠버네티스 환경을 사용한다. (쿠버네티스는 오픈소스이고, 대부분의 클라우드에서 지원하기 때문에 설치 방법은 크게 다르지 않다.)


참고 https://docs.google.com/document/d/1S5EaVR3Xq011JHcJQ0G84hkasboVUNUrcQzBlq9mJ14/edit

쿠버네티스 클러스터 생성

콘솔에서 아래 그림과 같이 istio 라는 이름으로 쿠버네티스 클러스터를 생성한다. 테스트용이기 때문에, 한존에 클러스터를 생성하고, 전체 노드는 3개 각 노드는 4 CPU/15G 메모리로 생성하였다.



다음 작업은 구글 클라우드 콘솔에서 Cloud Shell내에서 진행한다.

커맨드 라인에서 작업을 할것이기 때문에, gCloud SDK를 설치(https://cloud.google.com/sdk/gcloud/) 한후에,

%gcloud auth login

gcloud 명령어에 사용자 로그인을 한다.


그리고 작업을 편리하게 하기 위해서 아래와 같이 환경 변수를 설정한다. 쿠버네티스 클러스터를 생성한 리전과 존을 환경 변수에 아래와 같이 설정한다. 예제에서는 asia-southeast1 리전에 asia-southeast1-c 존에 생성하였다. 그리고 마지막으로 생성한 쿠버네티스 이름을 환경 변수로 설정한다. 예제에서 생성한 클러스터명은 istio이다.

export GCP_REGION=asia-southeast1
export GCP_ZONE=asia-southeast1-c
export GCP_PROJECT_ID=$(gcloud info --format='value(config.project)')
export K8S_CLUSTER_NAME=istio

다음 kubectl 명령어를 사용하기 위해서, 아래과 같이 gcloud 명령어를 이용하여 Credential을 세팅한다

% gcloud container clusters get-credentials $K8S_CLUSTER_NAME \
   --zone $GCP_ZONE \
   --project $GCP_PROJECT_ID

Credential 설정이 제대로 되었는지

% kubectl get pod -n kube-system

명령어를 실행하여, 쿠버네티스 시스템 관련 Pod 목록이 잘 나오는지 확인한다.

Istio 설치

쿠버네티스 클러스터가 준비되었으면, 이제 Istio를 설치한다.

Helm 설치

Istio는 Helm 패키지 매니져를 통해서 설치 한다.

% curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get > get_helm.sh
% chmod 700 get_helm.sh
% ./get_helm.sh

Istio 다운로드

Istio 를 다운로드 받는다. 아래는 1.0.4 버전을 다운 받는 스크립트이다.

% cd ~

% curl -L https://git.io/getLatestIstio | sh -

% cd istio-1.0.4

% export PATH=$PWD/bin:$PATH

Helm 초기화

Istio를 설치하기 위해서 Helm용 서비스 어카운트를 생성하고, Helm을 초기화 한다.

% kubectl create -f install/kubernetes/helm/helm-service-account.yaml

% helm init --service-account tiller

Istio 설치

다음 명령어를 이용하여 Istio를 설치한다. 설치시 모니터링을 위해서 모니터링 도구인 kiali,servicegraph 그리고 grafana 설치 옵션을 설정하여 아래와 같이 추가 설치 한다.

% helm install install/kubernetes/helm/istio \

--name istio \

--namespace istio-system \

--set tracing.enabled=true \

--set global.mtls.enabled=true \

--set grafana.enabled=true \

--set kiali.enabled=true \

--set servicegraph.enabled=true


설치가 제대로 되었는지 kubectl get pod명령을 이용하여, istio 네임스페이스의 Pod 목록을 확인해보자

% kubectl get pod -n istio-system




BookInfo 샘플 애플리케이션 설치

Istio 설치가 끝났으면, 사용법을 알아보기 위해서 간단한 예제 애플리케이션을 설치해보자, Istio에는 BookInfo (https://istio.io/docs/examples/bookinfo/)  라는 샘플 애플리케이션이 있다.

BookInfo 애플리케이션의 구조

아래 그림과 같이 productpage 서비스 안에, 책의 상세 정보를 보여주는 details 서비스와 책에 대한 리뷰를 보여주는 reviews 서비스로 구성이 되어 있다.  


시스템의 구조는 아래와 같은데, 파이썬으로 개발된 productpage 서비스가, 자바로 개발된 review 서비스과 루비로 개발된 details 서비스를 호출하는 구조이며, review 서비스는 v1~v3 버전까지 배포가 되어 있다. Review 서비스 v2~v3는 책의 평가 (별점)를 보여주는  Rating 서비스를 호출하는 구조이다



< 그림 Book Info 마이크로 서비스 구조 >

출처 : https://istio.io/docs/examples/bookinfo/

BookInfo 서비스 설치

Istio의 sidecar injection 활성화

Bookinfo 서비스를 설치하기 전에, Istio의 sidecar injection 기능을 활성화 시켜야 한다.

앞에서도 설명하였듯이 Istio는 Pod에 envoy 를 sidecar 패턴으로 삽입하여, 트래픽을 컨트롤 하는 구조이 다. Istio는 이 sidecar를 Pod 생성시 자동으로 주입 (inject)하는 기능이 있는데, 이 기능을 활성화 하기 위해서는 쿠버네티스의 해당 네임스페이스에 istio-injection=enabled 라는 라벨을 추가해야 한다.

다음 명령어를 이용해서 default 네임 스페이스에 istio-injection=enabled 라벨을 추가 한다.

% kubectl label namespace default istio-injection=enabled


라벨이 추가되었으면

% kubectl get ns --show-labels

를 이용하여 라벨이 제대로 적용이 되었는지 확인한다.


Bookinfo 애플리케이션 배포

Bookinfo 애플리케이션의 쿠버네티스 배포 스크립트는 samples/bookinfo 디렉토리에 들어있다. 아래 명령어를 실행해서 Bookinfo 앺ㄹ리케이션을 배포하자.

% kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml


배포를 완료한 후 kubectl get pod 명령어를 실행해보면 다음과 같이 productpage, detail,rating 서비스가 배포되고, reviews 서비스는 v1~v3까지 배포된것을 확인할 수 있다.



Kubectl get svc 를 이용해서 배포되어 있는 서비스를 확인하자



Prodcutpcage,rating,reviews,details 서비스가 배포되어 있는데, 모두 ClusterIP 타입으로 배포가 되어 있기 때문에 외부에서는 접근이 불가능하다.


Istio gateway 설정

이 서비스를 외부로 노출 시키는데, 쿠버네티스의 Ingress나 Service는 사용하지 않고, Istio의 Gateway를 이용한다.

Istio의 Gateway는 쿠버네티스의 커스텀 리소스 타입으로, Istio로 들어오는 트래픽을 받아주는 엔드포인트 역할을 한다. 여러 방법으로 구현할 수 있으나, Istio에서는 디폴트로 배포되는 Gateway는 Pod 형식으로 배포되어 Load Balancer 타입의 서비스로 서비스 된다.


먼저 Istio Gateway를 등록한후에, Gateway를 통해 서비스할 호스트를 Virtual Service로 등록한다.


아래는 bookinfo에 대한 Gateway를 등록하는 Yaml 파일이다.


apiVersion: networking.istio.io/v1alpha3

kind: Gateway

metadata:

 name: bookinfo-gateway

spec:

 selector:

   istio: ingressgateway # use istio default controller

 servers:

 - port:

     number: 80

     name: http

     protocol: HTTP

   hosts:

   - "*"


selector를 이용해서 gateway 타입을 istio에서 디폴트로 제공하는 Gateway를 사용하였다. 그리고, HTTP프로토콜을 80 포트에서 받도록 하였다.

다음에는 이 Gateway를 통해서 트래픽을 받을 서비스를 Virtual Service로 등록해야 하는데, 그 구조는 다음과 같다.


apiVersion: networking.istio.io/v1alpha3

kind: VirtualService

metadata:

 name: bookinfo

spec:

 hosts:

 - "*"

 gateways:

 - bookinfo-gateway

 http:

 - match:

   - uri:

       exact: /productpage

   - uri:

       exact: /login

   - uri:

       exact: /logout

   - uri:

       prefix: /api/v1/products

   route:

   - destination:

       host: productpage

       port:

         number: 9080


spec에서 gateways 부분에 앞에서 정의한 bookinfo-gateway를 사용하도록 한다. 이렇게 하면 앞에서 만든 Gateway로 들어오는 트래픽은 이 Virtual Servivce로 들어와서 서비스 디는데, 여기서 라우팅 룰을 정의 한다 라우팅룰은 URL에 때해서 어느 서비스로 라우팅할 지를 정하는데 /productpage,/login,/lougout,/api/v1/products URL은 productpage:9080 으로 포워딩해서 서비스를 제공한다.


Gateway와 Virtual service 배포에 앞서서, Istio에 미리 설치되어 있는 gateway를 살펴보면, Istio default gateway는 pod로 배포되어 있는데, istio=ingressgateway 라는 라벨이 적용되어 있다. 확인을 위해서 kubectl get 명령을 이용해서 확인해보면 다음과 같다.

%kubectl get pod -n istio-system -l istio=ingressgateway



이 pod들은 istio-ingressgateway라는 이름으로 istio-system 네임스페이스에 배포되어 있다. kubectl get svc로 확인해보면 다음과 같다.

%kubectl get svc istio-ingressgateway -n istio-system --show-labels



그러면 bookinfo를 istio gateway에 등록해서 외부로 서비스를 제공해보자

% istioctl create -f samples/bookinfo/networking/bookinfo-gateway.yaml


게이트 웨이 배포가 끝나면, 앞에서 조회한 Istio gateway service의 IP (여기서는 35.197.159.13)에 접속해서 확인해보자

브라우져를 열고 http://35.197.159.13/productpage 로 접속해보면 아래와 같이 정상적으로 서비스에 접속할 수 있다.



모니터링 툴

서비스 설치가 끝났으면 간단한 테스트와 함께 모니터링 툴을 이용하여 서비스를 살펴보자

Istio를 설치하면 Prometheus, Grafana, Kiali,Jaeger 등의 모니터링 도구가 기본적으로 인스톨 되어 있다. 각각의 도구를 이용해서 지표들을 모니터링 해보자

Grafana를 이용한 서비스별 지표 모니터링

Grafana를 이용해서는 각 서비스들의 지표를 상세하게 모니터링할 수 있다.

먼저 아래 스크립트를 사용해서 간단하게 부하를 주자. 아래 스크립트는 curl 명령을 반복적으로 호출하여 http://35.197.159.13/productpage 페이지를 불러서 부하를 주는 스크립이다.


for i in {1..100}; do

curl -o /dev/null -s -w "%{http_code}" http://35.197.159.13/productpage

done


다음 Grafana 웹 콘솔에 접근해야 하는데, Grafana는 외부 서비스로 노출이 안되도록 설정이 되어 있기 때문에 kubectl을 이용해서 Grafana 콘솔에 트래픽을 포워딩 하도록 하자. Grafana는 3000번 포트에서 돌고 있기 때문에, localhost:3000 → Grafana Pod의 3000 번 포트로 트래픽을 포워딩 하도록 설정하자


kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=grafana -o jsonpath='{.items[0].metadata.name}') 3000:3000 &


다음 localhost:3000 번으로 접속해보면 다음과 같은 화면을 볼 수 있다.

각 서비스 productpage,review,rating,detail 페이지의 응답시간과 OPS (Operation Per Sec : 초당 처리량)을 볼 수 있다.




각 서비스를 눌러보면 다음과 같이 서비스별로 상세한 내용을 볼 수 있다. 응답 시간이나 처리량에 대한 트렌드나, Request의 사이즈등 다양한 정보를 볼 수 있다.



Jaeger를 이용한 분산 트렌젝션 모니터링

다음은 Jaeger 를 이용해 개별 분산 트렌젝션에 대해서 각 구간별 응답 시간을 모니터링 할 수 있다.

Istio는 각 서비스별로 소요 시간을 수집하는데, 이를 Jaeger 오픈소스를 쓰면 손쉽게 모니터링이 가능하다.

마찬가지로 Jaeger 역시 외부 서비스로 노출이 되지 않았기 때문에, kubectl 명령을 이용해서 로컬 PC에서 jaeger pod로 포트를 포워딩하도록 한다. Jaerger는 16686 포트에서 돌고 있기 localhost:16686 → Jaeger pod:16686으로 포워딩한다.


kubectl port-forward -n istio-system $(kubectl get pod -n istio-system -l app=jaeger -o jsonpath='{.items[0].metadata.name}') 16686:16686 &


Jaeger UI에 접속해서, 아래는 productpage의 호출 기록을 보는 화면이다. 화면 상단에는 각 호출별로 응답시간 분포가 나오고 아래는 개별 트렉젝션에 대한 히스토리가 나온다.



그중 하나를 선택해보면 다음과 같은 그림을 볼 수 있다.



호출이 istio-ingressgateway로 들어와서 Productpage를 호출하였다.

productpage는 순차적으로 productpage → detail 서비스를 호출하였고, 다음 productpage→ reviews → ratings 서비스를 호출한것을 볼 수 있고, 많은 시간이 reviews 호출에 소요된것을 확인할 수 있다.


Servicegraph를 이용한 서비스 토폴로지 모니터링

마이크로 서비스는 서비스간의 호출 관계가 복잡해서, 각 서비스의 관계를 시각화 해주는 툴이 있으면 유용한데, 대표적인 도구로는 service graph라는 툴과 kiali 라는 툴이 있다. BookInfo 예제를 위한 Istio 설정에는 servicegraph가 디폴트로 설치되어 있다.


마찬가지로 외부 서비스로 노출 되서 서비스 되지 않고 클러스터 주소의 8088 포트를 통해서 서비스 되고 있기 때문에, 아래와 같이 kubectl 명령을 이용해서 localhost:8088 → service graph pod의 8088포트로 포워딩하도록 한다.


kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=servicegraph -o jsonpath='{.items[0].metadata.name}') 8088:8088 &


그 후에, 웹 브루우져에서 http://localhost:8088/dotviz 를 접속해보면 서비스들의 관계를 볼 수 있다.



다음 글에서는 예제를 통해서 Istio에서 네트워크 경로 설정하는 부분에 대해서 더 자세히 알아보도록 하겠다.


쿠버네티스 리소스(CPU/Memory)할당과 관리

조대협

리소스 관리


쿠버네티스에서 Pod를 어느 노드에 배포할지를 결정하는 것을 스케쥴링이라고 한다.

Pod에 대한 스케쥴링시에, Pod내의 애플리케이션이 동작할 수 있는 충분한 자원 (CPU,메모리 등)이 확보되어야 한다. 쿠버네티스 입장에서는 애플리케이션에서 필요한 자원의 양을 알아야, 그 만한 자원이 가용한 노드에 Pod를 배포할 수 있다.


쿠버네티스에서는 이런 컨셉을 지원하기 위해서 컨테이너에 필요한 리소스의 양을 명시할 수 있도록 지원하고 있다.  현재(1.9 버전) 지원되는 리소스 타입은 CPU와 메모리이며, 아직 까지는 네트워크 대역폭이나 다른 리소스 타입은 지원하고 있지 않다.

리소스 단위

리소스를 정의하는데 사용되는 단위는 CPU의 경우에는 ms(밀리 세컨드)를 사용한다. 해당 컨테이너에 얼마만큼의 CPU 자원을 할당할것인가인데, 대략 1000ms가 1 vCore (가상 CPU 코어) 정도가 된다. 클라우드 벤더에 따라 또는 쿠버네티스를 운영하는 인프라에 따라서 약간씩 차이가 있으니 참고하기 바란다.

메모리의 경우에는 Mb를 사용한다.

Request & Limit

컨테이너에 적용될 리소스의 양을 정의하는데 쿠버네티스에서는 request와 limit이라는 컨셉을 사용한다.

request는 컨테이너가 생성될때 요청하는 리소스 양이고, limit은 컨테이너가 생성된 후에 실행되다가 리소스가 더 필요한 경우 (CPU가 메모리가 더 필요한 경우) 추가로 더 사용할 수 있는 부분이다.


예를 들어 CPU request를 500ms로 하고, limit을 1000ms로 하면 해당 컨테이너는 처음에 생성될때 500ms를 사용할 수 있다. 그런데, 시스템 성능에 의해서 더 필요하다면 CPU가 추가로 더 할당되어 최대 1000ms 까지 할당될 수 있다.


리소스를 정의하는 방법은 아래와 같이 Pod spec 부분에서 개별 컨테이너 마다. Resources 파트에 request와 limit으로 필요한 리소스의 최소/최대양을 정의하면 된다.


apiVersion: v1

kind: Pod

metadata:

 name: frontend

spec:

 containers:

 - name: db

   image: mysql

   resources:

     requests:

       memory: "64Mi"

       cpu: "250m"

     limits:

       memory: "128Mi"

       cpu: "500m"

 - name: wp

   image: wordpress

   resources:

     requests:

       memory: "64Mi"

       cpu: "250m"

     limits:

       memory: "128Mi"

       cpu: "500m"


위의 예제에 따라서 정의된 Pod내의 컨테이너 CPU 리소스의 할당은 다음과 같이 된다.


db라는 이름과 wp라는 이름의 컨테이너는 생성시 250ms 만큼의 CPU 리소스를 사용할 수 있도록 생성이 되고, 필요시 최대 CPU를 500ms 까지 늘려서 사용할 수 있다.

모니터링 리소스

그러면 사용할 수 있는 리소스의 양과 현재 사용되고 있는 리소스의 양을 어떻게 모니터링할 수 있을까?

사용할 수 있는 리소스의 양은 쿠버네티스 클러스터를 생성하는데 사용된 node의 스펙을 보면 알 수 있다. 예를 들어 2 코어 VM 5대로 node를 만들었다면 그 총량은 10 코어 = 10,000ms가 된다.

그러나 이 자원을 모두 사용자 애플리케이션에 사용할 수 있는 것이 아니다. 쿠버네티스 클러스터를 유지하는 시스템 자원이나 또는 모니터링등에 자원이 소비되기 때문에 실제로 사용할 수 있는 자원의 양을 확인하는게 좋은데 “kubectl describe nodes” 명령을 이용하면 된다.

아래 예제는 kubectl describe nodes 명령으로 node들의 상세정보중에서 한 node의 자원 상태를 모니터링한 내용이다.



아래 붉은 박스를 보면 총 4 코어 머신으로 현재 request된 CPU는 1081m이고 limit으로 296m를 확보하고 있다. 메모리는 request 된것은 685M가 requested 되었고, 약 1G가 limit으로 확보되어 있다.

실제 사용량은 붉은 박스 위를 보면 되는데, default 네임 스페이스의 client-6bcxxx Pod는 현재 110m의 CPU를 request해서 사용중인것을 확인할 수 있다.


확보된 리소스와 현재 실제로 사용되는 리소스의 양은 다른데, “kubectl top nodes” 명령을 이용하면 실제로 사용되고 있는 리소스의 상태를 확인할 수 있다. 아래는 4개의 노드에서 실제로 사용되고 있는 리소스의 양이다. 붉은 색으로 표시된 노드가 위의 예제와 같은 노드인데, 위에서 requested 된 양은 1081m이었는데, 실제 사용된 cpu는 151m가 사용되고 있다.


Pod들의 리소스 사용량은 “kubectl top pods” 명령으로 확인이 가능하다.


ResourceQuota & LimitRange

이제까지 컨테이너 운영에 필요한 리소스의 양을 명시하여 요청하는 방법을 알아보았다.

만약에 어떤 개발자나 팀이 불필요하게 많은 리소스를 요청한다면, 쿠버네티스 클러스터를 운영하는 입장에서 자원이 낭비가 되고, 다른 팀이 피해를 볼 수 있는 상황이 될 수 있다. 그래서, 쿠버네티스에서는 네임스페이스별로 사용할 수 있는 리소스의 양을 정하고, 컨테이너마다 사용할 수 있는 리소스의 양을 지정할 수 있는 기능을 제공한다.

Resource Quota

Resource Quota는 네임스페이스별로 사용할 수 있는 리소스의 양을 정한다.

아래는 예는 demo 네임스페이스에, CPU 는 500m ~ 700m 까지, 메모리는 100M~500M까지 할당한 예제이다.



이 용량안에서 demo 네임스페이스내에 컨테이너를 자유롭게 만들어서 사용할 수 있다.

Limit Range

Resource Quota가 네임 스페이스 전체의 리소스양을 정의한다면, Limit Range는 컨테이너 개별 자원의 사용 가능 범위를 지정한다.

아래 예제를 보자.



  • default 로 정의된 부분은 컨테이너에 limit을 지정하지 않았을 경우 디폴트로 지정되는 limit이다. 여기서는 cpu 600m, 메모리 100m로 정의되었다.

  • defaultRequest 로 정의된 부분은 컨테이너의 request를 지정하지 않았을 경우 디폴트로 지정되는 request의 양이다.

  • max : 컨테이너에 limit을 지정할 경우, 지정할 수 있는 최대 크기이다.

  • min : 컨테이너에 limit을 지정할 경우, 지정할 수 있는 최소 크기이다.  

Overcommitted 상태

이  request와 limit의 개념이 있기 때문에 생기는 문제인데, request 된 양에 따라서 컨테이너를 만들었다고 하더라도, 컨테이너가 운영이되다가 자원이 모자르면 limit 에 정의된 양까지 계속해서 리소스를 요청하게 된다.

컨테이너의 총 Limit의 양이 실제 시스템이 가용한 resource의 양보다 많을 수 있는 경우가 발생한다. 이를 overcommitted 상태라고 한다.

Overcommitted 상태가 발생하면, CPU의 경우에는 실제 사용량을 requested 에 정의된 상태까지 낮춘다. 예를 들어 limit이 500, request가 100인 경우, 현재 500으로 가동되고 있는 컨테이너의 CPU할당량을 100으로 낮춘다. 그래도 Overcommitted 상태가 해결되지 않는 경우, 우선 순위에 따라서 운영중인 컨테이너를 강제 종료 시킨다.  

메모리의 경우에는 할당되어 사용중인 메모리의 크기를 줄일 수 는 없기 때문에, 우선 순위에 따라서 운영 중인 컨테이너를 강제 종료 시킨다.  Deployment,RS/RC에 의해 관리되고 있는 컨테이너는 다시 리스타트가 되고 초기 requested 상태의 만큼만 자원 (메모리/CPU)를 요청해서 사용하기 때문에, overcommitted  상태가 해제된다.

Best practice

구글 문서에 따르면 데이타 베이스등 아주 무거운 애플리케이션이 아니면, 일반적인 경우에는 CPU request를 100m 이하로 사용하기를 권장한다.

또한 세밀하게 클러스터를 운영하기 어려운 경우에는 request와 limit의 사이즈를 같게 하는 것을 권장한다. limit이 request보다 클 경우 overcommitted 상태가 발생할 수 있는데, 이때 CPU가 throttle down 되면, 실제 필요한 CPU양 보다 작은 CPU양으로 줄어들기 때문에 성능저하가 발생할 수 있다.  




쿠버네티스 보안 Best Practice


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


지금까지 여러가지 보안 기능에 대해서 알아보았다. 그러면 이러한 보안 기능을 어떻게 잘 사용할지 베스프 프렉틱스에 대해서 알아보자. 쿠버네티스 보안 베스트 프렉틱스는 쿠버네티스 공식 블로그 https://kubernetes.io/blog/2016/08/security-best-practices-kubernetes-deployment/ 에 2016년 8월에 포스팅과 https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/ 에 2018년 7월에 포스팅된 내용을 기반으로 한다. 쿠버네티스는 새버전 릴리즈가 빠른 편이고 버전마다 기능이나 아키텍쳐의 변화가 심하기 때문에, 항상 새로운 베스트 프렉틱스를 찾아서 참고하기 바란다.

이 글에서는 쉽지만 중요한 보안 정책을 위주로 설명한다.

Control plan security

TLS (SSL) 사용

쿠버네티스를 설치하면 디폴트로 API 통신은 TLS(SSL) 암호화를 이용하도록 되어 있으나, 일부 재배포판의 경우는 REST API통신을 HTTPS를 사용하지 않고, HTTP를 사용하는 경우가 있기 때문에, 이를 확인할 필요가 있다.


인증

쿠버네티스는 앞에서 설명한데로 여러가지 인증 방식을 제공하고 있는데, 그중에 BASIC_AUTH를 사용하는 방식등은, 비밀 번호가 그냥 네트워크를 통해서 전송되기 때문에, 중간에 패킷을 가로 채는 방식등으로 탈취가 가능하다.

쿠버네티스는 이외에도 Bootstrap token, static token, X509 인증서, Open ID 연동등 다양한 인증 방식이 있는데, 가급적이면 Open ID 인증 방식을 사용하는 것이 안전하다.

RBAC 사용

쿠버네티스의 기본 인증 방법은 ABAC(Attirbute-Based control) 이다.  사용자마다 기능에 대해서 권한을 배정하는 방법인데,

RBAC은 1.6에서 소개되었고, 1.8 부터는 디폴트이다. 1.6~1.8 버전은 RBAC 설정을 따로 하기 바라고, 1.6 이하 버전은 1.8 이상 버전으로 업그레이드 하는 것을 권장한다.

대쉬 보드 사용 금지

쿠버네티스 대쉬 보드는 편리하고 강력한 기능을 가지고 있지만, 별도의 접근 통제 기능이 디폴트로 탑재되어 있지 않다. 1.8 이전 버전에는 클러스터에 대한 모든 억세스가 가능한 서비스 어카운트가 바인딩되어 있기 때문에, 클러스터에 대한 모든 접근이나 보안 정보(토큰)등의 탈취가 가능하다.

실제로 테슬라의 경우에 쿠버네티스 대쉬보드 접근을 통해서, 해킹을 당한 사례가 유명하다. https://redlock.io/blog/cryptojacking-tesla

가급적이면 쿠버네티스 대쉬보드를 사용하지 않도록 하는 것이 좋고 (인스톨하지 않는다.), 사용한다고 해도, 계정 인증과, 내부 인터넷망을 통한 접근만을 허용하는등의 추가적인 보안 조치가 반드시 필요하다.

kubectl 억세스 통제 (마스터 노드 억세스 통제)

kubectl은 쿠버네티스를 통제할 수 있는 매우 강력한 툴이다. 일종의 어드민툴이기 때문에 접근 제어를 하는 것이 좋다. 쿠버네티스 방화벽 설정등을 해서 특정 머신에서만 오는 트래픽만 마스터 노드가 받아 드리도록 설정하는 방법이다.  태그 기반으로 k8s-controller로 가는 트래픽을 특정 머신에서 오는 트래픽만을 수용하게 하거나 또는 bastion을 놓고, bastion에서 들어온 API 호출만 수용하도록 하는 방법이 있다.

서비스 어카운트 토큰 마운트을 자동으로 마운트 하지 않게 한다.

Pod는 기본적으로 서비스 어카운트를 사용하게 되어 있다. 만약 서비스 어카운트를 지정하지 않으면 디폴트로 정의된 서비스 어카운트를 사용하게 되는데, 쿠버네티스에서는 디폴트로 서비스 어카운트를 사용하게 되면 서비스 어카운트의 API 토큰을 볼륨으로 마운트 한다.

서비스 어카운트 볼륨은 /var/run/secrets/kubernetes.io/serviceaccount 디렉토리에 마운트 되는데, 이 디렉토리 안에는 API인증을 위한 인증서와, 토큰이 들어 있다.

만약 이 토큰을 탈취당하게 되면, 토큰을 이용하여 쿠버네티스 API 접근이 가능하다.

일반적인 Pod의 경우에는 애플리케이션을 운영하기 위한 목적으로 사용될뿐, 쿠버네티스 API를 접근할 일이 없기 때문에 사용하지 않는 토큰을 마운트 하는 것은 위험하다. 해커가 컨테이너를 해킹해서  /var/run/secrets/kubernetes.io/serviceaccount 디렉토리를 접근한다면 토큰을 탈취할 수 있다.

이를 막기 위해서, 서비스 어카운트를 사용시 디폴트로 서비스 어카운트 토큰을 마운트하지 않게 하는 것이 좋다.


아래와 같이 서비스 어카운트를 생성할때, 자동으로 토큰을 마운트 하지 않는 옵션을 주거나,

apiVersion: v1

kind: ServiceAccount

metadata:

 name: nonroot-sa

automountServiceAccountToken: false


또는 아래와 같이 Pod 정의 부분에서 서비스 어카운트에 디폴트로 토큰을 마운트 하지 않게 정의하면 된다.


apiVersion: apps/v1

kind: Deployment

metadata:

 name: nonroot-deploy

spec:

 replicas: 3

 selector:

   matchLabels:

     app: nonroot

 template:

   metadata:

     name: nonroot-pod

     labels:

       app: nonroot

   spec:

     serviceAccountName: nonroot-sa

     automountServiceAccountToken: false

     securityContext:

       runAsUser: 1001

       fsGroup: 2001

     containers:

     - name: nonroot

       image: gcr.io/terrycho-sandbox/security-context:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080


반대로 토큰을 사용하고 싶을때는 Pod 정의 부분에서 automountServiceAccountToken: true 옵션을 주면된다.


감사 로깅 (Audit)

쿠버네티스 클러스터에 대한 각종 명령어 (Pod 생성, Service 생성 삭제 등)에 대한 내용을 추적하기 위해서 모든 로그를 남겨서 정상적인 접근 여부를 판단하고, 비정상적인 접근이 발생하였을때, 이를 감지하고 추적할 수 있는 기반을 마련해야 한다. 모니터링/로깅 시스템 구축시 감사 로깅은 별도로 분리해서 감사에 대한 내용만을 따로 추적할 수 있도록 하는것이 좋다.

노드 시큐리티

컨테이너를 호스팅하는 노드에 대해서도 보안 조치가 필요하다. 다음 항목은 필수적으로 적용하기를 권장되는 항목이다.

노드에 Private IP 만 사용 (Public IP 사용 금지)

노드 서버의 IP를  Private IP만을 사용하여, 외부 인터넷으로 부터 노드 서버를 접속할 수 있는 경로를 원천적으로 차단한다.

Minimal OS 사용

노드 서버의 OS는 필요한 기능만을 가지고 있는 OS만을 설치하는 것이 좋다. OS에 따라서 디폴트로 메일 서버, FTP등 사용하지 않는 서비스가 디폴트로 제공됨으로써, 노드 서버로 접근할 수 있는 채널을 제공할 수 있다.

정기적인 패치

또한 노드 OS에 대해서 정기적인 보안 패치를 적용함으로써, 새롭게 발견되는 보안 위협에 대해 사전 봉쇄 조치를 취해야 한다

컨테이너 시큐리티

마지막으로 컨테이너에 대한 보안을 강화하는 방법이다.

컨테이너 이미지 관리

가장 중요한 것중의 하나가 컨테이너 이미지를 잘 관리하는 것인데, 일반적으로 도커로 이미지를 만들때, 베이스 이미지 (OS등이 깔려있는)를 외부 컨테이너 레파지토리에서 가져다가 사용하는 경우가 많다. 이 경우 공인되지 않는 이미지등을 사용해서, 해킹 프로그램이 깔려있는 이미지가 베이스 이미지로 사용되거나, 또는 최신 보안 패치가 되어 있지 않은 이미지로 전환하지 않고 계속 오래된 이미지를 사용해서, 보안에 헛점을 들어내는 경우가  많다.

베이스 이미지 사용은 반드시, 보안이 검증된 이미지를 사용하되, 지속적으로 최신 OS 패치를 적용한 이미지를 사용해야 한다.

베이스 이미지를 포함하여 실제 쿠버네티스에 배포되는 애플리케이션 컨테이너 이미지는 신뢰할 수 있는 이미지 저장 서비스를 이용하고, 해당 클러스터 및 허가된 IP나 사용자만 접근할 수 있도록 하는 것이 좋다.


또는 상용 서비스 중에는 컨테이너 저장소에 저장된 이미지를 스캔해서 보안에 위협이 되는 항목을 자동으로 검출하여 알려주는 서비스들이 있다.

아래 그림은 구글 클라우드의 컨테이너 저장소 서비스로, 컨테이너에 저장된 이미지에 대해서 보안 위협을 자동으로 스캔해서 리포팅 해주는 기능이다.




Security Context 사용

Pod를 정의할때, 불필요한 root나 커널 접근 권한을 최대한 제외하는 것이 좋다.

Security context를 이용해서 이런 권한을 통제할 수 있다.

  • 컨테이너는 꼭 필요하지 않는 이상 root 사용자 권한이 아니라 일반 사용자로 실행하도록 한다. securityContext에서 runAsUser와 fsGroup을 이용해서 사용자와 그룹을 지정할 수 있다.

  • Root 권한으로 실행할 수 없도록 securityContext에서 runAsNonRoot 를 true로 설정한다.

  • 꼭 필요한 경우가 아니라면 root 권한으로 생성된 파일이나 디렉토리에 대해서는 읽기만을 할 수 있도록 SecurityContext에서 readOnlyRootFilesystem 를 true로 설정한다.

  • 마지막으로 필요한 경우가 아니라면 호스트 커널에 대한 접근을 막기 위해서 securityContext에서 privileged를 false로 설정한다.

PodSecurityPolicy를 이용한 Pod Security Context 통제

SecurityContext를 위와 같이 설정하도록 권고하지만, 이를 빼먹을 수 있기 때문에,  PodSecurityPolicy (PSP)를 정의하여, Security context를 강제할 필요가 있다.

Network Policy를 이용한 트래픽 통제

마지막으로 Network policy를 정의해서, Pod로의 네트워크 접근을 통제하여 불필요한 접근을 막는다. 예를 들어 MySQL DB서버로의 접근은 label이 app=apiserver인 서버들만 3306으로 inbound 트래픽만 들어올 수 있도록 통제하는 등의 예를 들 수 있다.



아두이노 nodemcu로 온습도계를 만들어 보자


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


nodeMCU 개발환경 설정이 끝났으면 간단한 애플리케이션을 하나 만들어보자

온습도계를 만들어보도록 한다. 온습도를 측정하여 LED로 출력하는 모듈을 개발해보겠다.

개발이 끝나고 나서 아두이노 개발환경에 대한 결론 부터 이야기 하자면, 쉽다. 대부분의 파츠들에 대한 SDK가 제공되기 때문에 손쉽게 개발이 가능하다. 단 해당 파츠에 맞는 SDK를 찾는데 들억는 시간이 더 많다.

온습도계 센서 (DTH11)

개발에 사용할 온습도계 센서는 DTH11이라는 센서이다. 아래와 같이 생겼는데, 좌측이 데이타, 가운데가 3.3V, 가장 우측이 GND이다.



온도와 습도 두개를 측정하는데 데이타 단자가 하나이다. 아날로그 신호를 핀에서 직접 읽는 것이 아니라 SDK를 사용한다.  DTH11 라이브러리는 https://github.com/adafruit/DHT-sensor-library 에서 다운 받아서 사용하면 된다. Adafruit_sensor 라이브러리에 대한 의존성이 있기 때문에, 해당 라이브러리 https://github.com/adafruit/Adafruit_Sensor 도 같이 설치하도록 한다.


라이브러리 설치가 끝났으면, DTH11 센서를 브레드 보드에 설치한다 좌측을 GPIO G6 포트에, 가운데를 3.3V, 가장 우측은 GND에 연결한다.


I2C LCD

다음 습도와 온도를 출력하기 위해서 LCD를 사용한다. 여기서 사용한 LCD는 I2C LCD로 가로 16자로 2줄 (16x2) 를 출력할 수 있는 LCD이다.


앞판은 LCD가 있고




뒤에는 아래 그림과 같이 LCD 아답터가 붙어 있다.

  • VCC는 nodemcu의 Vin 핀에 연결한다. 이 핀은 5V의 전앞을 낸다.

  • GND는 nodemcu의 GND에

  • SCA는 nodemcu D2에

  • SCL은 nodemcu의 D1핀에 연결한다.



다음 이 LCD를 사용하기 위해서는 SDK를 설치해야 하는데, https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library 라이브러리를 다운 받아서 설치한다.


코드

이제 코드를 보자

#include <LiquidCrystal_I2C.h>

#include "DHT.h"


#define LED D5            // Led in NodeMCU at pin GPIO16 (D0).

#define DHTPIN D6

#define DHTTYPE DHT11   // DHT 11


LiquidCrystal_I2C lcd(0x27, 16, 2);

DHT dht(DHTPIN, DHTTYPE);

float h,t;

void setup() {

 pinMode(LED, OUTPUT);    // LED pin as output.

 dht.begin();


 // LCD setting

 lcd.begin();

 lcd.backlight();

 lcd.print("Hello, world!");

 

 // Serial communication setting

 Serial.begin(9600);   

 Serial.print("Hello nodemcu");

}

void loop() {

 digitalWrite(LED, HIGH);// turn the LED off.(Note that LOW is the voltage level but actually

                         //the LED is on; this is because it is acive low on the ESP8266.

 delay(200);            // wait for 1 second.

 digitalWrite(LED, LOW); // turn the LED on.

 delay(200); // wait for 1 second.

 h = dht.readHumidity();

 t = dht.readTemperature();

 lcd.setCursor(0,0);

 lcd.print("Humidity:");

 lcd.print(h);

 lcd.setCursor(0,1);

 lcd.print("Temp :");

 lcd.print(t);

 Serial.print("H:");

 Serial.print(h);

 Serial.print(" T:");

 Serial.println(t);

}


DHT11 관련 코드

DHT11을 사용하려면, 입력 포트를 정의해야 하고 센서의 종류를 정의해야 한다.

#define DHTPIN D6

#define DHTTYPE DHT11   // DHT 11


DHT dht(DHTPIN, DHTTYPE);


에서 핀은 D6로 지정하고 DHTTYPE은 DHT11로 정의하였다.

다음 센서를 가동 시키기 위해서 setup() 에서


 dht.begin();


와 같이 DHT 센서를 가동 시켰다. 다음은 센서에서 온도와 습도를 읽어와야 하는데, loop() 함수내에서


 h = dht.readHumidity();

 t = dht.readTemperature();


readHumidity()와 readTemperature() 함수를 이용하여, 습도와 온도를 읽어왔다.


LCD 관련 코드

LCD를 사용하려면 초기화를 해줘야 하는데


LiquidCrystal_I2C lcd(0x27, 16, 2);


로 초기화를 해준다. 0x27는 LCD의 주소로, D1,D2 핀을 사용할때 사용하는 주소이다.핀의 위치가 바뀌면 이 주소도 변경 되어야 한다. 핀에 위치에 따라 주소가 다르거나 또는 인식이 안되는 경우가 있는데, http://playground.arduino.cc/Main/I2cScanner 를 이용하면 LCD가 연결이 되어 있는지 아닌지, 그리고 LCD의 주소를 알려준다.

다음 인자인 16,2는 가로 16자에 세로 2자 LCD 임을 정의해준다.


다음 초기화를 해줘야 하는데, setup()함수에서

 lcd.begin();

 lcd.backlight();

 lcd.print("Hello, world!");


로 초기화를 해준다. begin()은 LCD를 시작하는 것이고 backlight()는 LCD의 백라이트를 키도록 하는것이다. 글자를 출력하고 싶으면 print(“문자열")을 이용하면된다.

초기화가 끝났으면, DTH11 센서에서 읽은 값을 출력해주면된다.

윗줄에 습도 아랫줄에 온도를 출력할것인데

 lcd.setCursor(0,0);

 lcd.print("Humidity:");

 lcd.print(h);

 lcd.setCursor(0,1);

 lcd.print("Temp :");

 lcd.print(t);


윗줄 첫번째 위치 부터 습도를 출력할것이기 때문에, 출력 위치를  setCursor(0,0)으로 해서 맨 앞칸 첫줄로 지정을 해서 습도를 출력하고, 다음 온도는 setCursor(0,1)로 해서 맨 앞칸 두번째 줄 부터 출력하도록 하였다.



<그림. 완성된 모습 >

다음글에서는 이렇게 수집한 정보를 HTTP 를 이용해서 서버로 전송하는 코드를 추가해보도록 한다.


참고 자료


라즈베리 파이 호환 보드 조사


라즈베리 파이를 구입해놓고 보니 단순한 소형 리눅스 머신이나. IO를 위한 GPIO 포트가 있는것 빼고는 크게 다르지 않다. 아두이노 시리즈도 호환 보드가 있는것 처럼, 라즈베리파이도 호환 보드가 있을것으로 생각하고 찾아보니 당연히 있다.

Orange PI



우분투,Debian, 라즈베리 파이 이미지 실행 가능


이름

CPU/GPU

메모리

디스크

가격

오렌지 파이 PC+

1.6Ghz 4 core

600Mhz GPU (OPEN GL ES)

1G

8G Flash

37500

오렌지파이 PC2

64bit 4 core, GPU 내장

1G

2MB Flush

40000

오렌지파이 라이트

4 core, 600MHz GPU

512M


25000


Banana PI


국내에서는 판매하는 곳이 많지 않고, 알리바바나 아마존을 통해서 구입해야 하는데, 기존 호환 보드에 비해서 8Core CPU를 탑재한 모델(12만원)이 있는등, 성능이 뛰어나다. 가격은 고가인편. 성능이 좋은편인데, 국내에서 많이 보편화 되지 않은 것은 아무래도 가격 때문인듯

Asus Tinker Board


1.8 GHZ, 4 코어 CPU, 600 Mhz GPU, 2GB 메모리, 가격대는 15만원선 국내에서는 판매하는 곳이 그리 많지 않음


비글본 블랙



1GHz Arm CPU, GPU 지원, 512M, 4G Flash, WIFI 지원으로 약 13만원선


국내에서 구매가 편리하고 가격대가 좋은 보드로는 오렌지 보드가 가성비 측면에서 가장 좋지 않은가 싶다.

라즈베리파이 3B+가 64비트 쿼드 코어 + 1G 메모리에 43000원 정도의 가격을 형성하는데.

오렌지 보드 PC+의 경우 32비트 쿼드코어 + 1G 메모리에 8G 온보드 플래시를 지원하면서 37000원정도이다.  PC2 모델의 경우 64비트 쿼드코어에 1G메모리에 플래시는 탑재하지 않고 40000원 수준이다.

크게 가격적인 차이는 없지만, 세세한 스펙상의 차이가 있기 때문에, 호환 보드를 고민해도 좋지 않을까 싶은데, 가격적인 차이가 크지 않기 때문에, 개발을 시작하는 입장에 있어서는 코드나 장비 호환성 측면에서 라즈베리 파이로 시작하는게 좋을것 같고, 소형 장비를 개발할때는 오렌지 보드를 고민하는 것도 좋지 않을까 한다.


ESP01 (ESP8266)을 이용한 HTTP 통신


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


하드웨어 시리얼과 소프트웨어 시리얼

앞의 글에서 ESP01을 연결해봤는데, ESP01 연결시에 포트를 2,3번 포트를 사용하고, 코드는 SoftSerial 라이브러리를 이용하였다. 아두이노 우노의 0,1 번포트는 시리얼 통신을 위한 RX,TX 포트이다. 이를 Hardware Serial 이라고 한다. 그러면 하드웨어 시리얼 포트를 사용하지 않고 2,3번 포트를 사용한 후에 소프트웨어 시리얼 처리를 한 이유는 무엇을까?


하드웨어 시리얼 포트는 PC와 연결되어 있을때 PC와 통신을 목적으로 사용된다. 그래서 하드웨어 시리얼을 사용하지 않은것인데, ESP8266 관련 라이브러리들을 살펴보니, 대부분 하드웨어 시리얼을 사용하는 것으로 보인다. 그러면 이 문제를 어떻게 해결할것일까?


아두이오 우노 기판을 보면 TX와 RX라고 써있는 LED 가 있는데, 하드웨어 시리얼 통신을 할때만 깜빡 거린다.




PC에 연결이 되어 있을 때, AT 명령을 실행할때는 이 LED가 빛나지 않고, 코드를 업로드 할때만 빛이 난다. 즉 코드 업로드 시에만 하드웨어 시리얼을 PC가 사용한다는 것이기 때문에, 코드를 업로드한후에 ESP01 보드를 TX,RX 단자에 연결하면 하드웨어 시리얼을 사용할 수 있다.

아두이노를 이용한 HTTP 통신

아두이노에 ESP01 모듈을 연결했으면, HTTP 통신을 해보도록 한다. ESP01 (ESP8266 계열)은 TCP/UDP 스택이 이미 들어가 있기 때문에, TCP 통신은 가능하다. HTTP 통신을 하려면 TCP 프로토콜을 이용하여 HTTP 메세지를 보내주면 되는데, AT 명령어를 사용하면 된다.

AT 명령어 레퍼런스는 https://www.electrodragon.com/w/ESP8266_AT-Command_firmware 를 참고하기 바란다. AT명령을 이용하여 WIFI 네트워크에 연결하는 방법은 http://bcho.tistory.com/1283 에 설명되어 있다. WIFI 네트워크에 연결되어 있다고 가정하고 AT명령을 이용해서 HTTP 명령을 보내는 방법을 보자


HTTP  서버 연결

HTTP 요청을 보내기 위해서 먼저 웹서버에 TCP 커넥션을 연결해야 한다. TCP 커넥션 연결 명령어는 AT+CIPSTART로 다음과 같다.


AT+CIPSTART={TCP 연결 번호},”TCP”,”웹서버IP”,{포트번호}


이 명령은 해당 웹서버 IP의 포트로 연결을 하고 연결 번호(이름)를  “0”으로 지정한것이다.

210.118.92.89 서버의 80 포트로 연결하려면 Serial 콘솔에서 AT+CIPSTART=0,”TCP”,”210.118.92.89”,80 명령어를 실행하면 된다. 이 연결의 연결 번호는 0 번이 된다.

명령어 전달

서버로 연결이 되었으면, HTTP 명령어를 보내야 하는데, 명령어 전달은 AT+CIPSEND 명령어를 사용하면 된다. 명령어의 형식은 다음과 같다.


AT+CPISEND={TCP 연결번호},{보내고자 하는 메세지 길이}


예를 들어 “GET /index.html” 명령을 0번 TCP 연결로 보내고자 하면, 메세지의 길이가 15자이기 때문에,

AT+CPISEND=0,15 로 실행하고, 그 다음에

> 가 나오면 GET /index.html 을 전송하면 된다.

예제

실제 개발에서는 콘솔에서 AT 명령을 직접 입력해서 사용하는 것이 아니기 때문에, 이를 코드화 해야 하는데, 아래는 코드화 한 예제이다. (실제 개발에서는 사용하지 않을것이기 때문에 테스트는 하지 않았다)

#include <SoftwareSerial.h>


SoftwareSerial esp(2,3); //TX,RX

String SSID="{Wifi SSID}";

String PASSWORD="{Wifi password}";


void connectWifi(){

 String cmd = "AT+CWMODE=1";

 esp.println(cmd);

 cmd ="AT+CWJAP=\""+SSID+"\",\""+PASSWORD+"\"";

 esp.println(cmd);

 delay(1000);

 if(esp.find("OK")){

   Serial.println("Wifi connected");

 }else{

   Serial.println("Cannot connect to Wifi"+esp);

 }

}


void httpGet(String server,String uri){

 String connect_server_cmd = "AI+CIPSTART=4,\"TCP\",\""+server+"\",80";

 esp.println(connect_server_cmd);

 String httpCmd = "GET "+uri;

 String cmd = "AT+CIPSEND=4,"+httpCmd.length();

 esp.println(cmd);

 esp.println(httpCmd);


}

void setup() {

 // put your setup code here, to run once:

 esp.begin(9600);

 Serial.begin(9600);

 connectWifi();

 httpGet("210.192.111.122","/index.html");

}


void loop() {

 // put your main code here, to run repeatedly:


}


위의 붉은 색으로 표시된 부분을 보면 번호 4번으로 TCP연결을 열고, 4번 채널에 AT+CIPSEND 명령을 이용하여 GET /index.html 을 실행하였다.

SDK의 활용

HTTP 통신의 기본 원리를 이해하였으면, 이제 실제로 HTTP 호출을 해보자. AT 명령을 이용해도 되지만,  HTTP 명령어 종류가 많고, 연결, 메세지 전송/받기등 다양한 명령을 AT 명령으로 직접 코딩하기에는 코딩양이 많고 복잡해진다. 그래서 이런 명령을 잘 추상화하여 단순화 해놓은 SDK를 사용하는 것이 좋은데, 아두이노가 오픈 소스인 만큼, 오픈소스 기반의 SDK가 많다. 장점이기도 하지만 반대로 단점도 되는데, 아두이노나 ESP8266 펌웨어의 버전이 낮으면 동작하지 않는 경우도 있고, 또한 많은 라이브러리들이 아두이노 WIFI 실드를 기준으로 개발되었거나 또는 아두이노 → ESP8266으로 통신하는 용도가 아닌 ESP8266 기반의 아두이노 보드를 기준으로 개발된 라이브러리가 많다.

아두이노 라이브러리 추가하기

우리가 사용할 라이브러리는 WiFiESP라는 라이브러리 (https://github.com/bportaluri/WiFiEsp) 로 소프트웨어 시리얼과 하드웨어 시리얼 통신 양쪽을 모두 지원하며, HTTP 형태로 라이브러리가 잘 패키징 되어 있다. 또한 예제가 비교적 잘 정리되어 있어서 사용하기 좋다.

이런 오픈소스 라이브러리를 사용하려면 라이브러리를 설치해야 하는데, 설치 방법은 다음과 같다.

github에 배포된 코드의 경우, 해당 코드를 아래 그림과 같이 download 버튼을 이용하여 zip 파일로 다운로드 받는다.




다음 Sketch에서 Sketch > Include Library > Add .Zip Library 를 선택해서 앞에서 다운 받은 ZIP 파일을 선택하면 라이브러리로 등록되어 사용이 가능하다.




SDK를 이용하여 HTTP 호출


아래코드는 라이브러리를 추가한후에, 예제에 나온 코드를 그대로 사용한 예이다. 아두이노 2,3번 핀을 ESP01의 TX,RX 핀에 연결해서 소프트웨어 시리얼로 통신하였다. 그래서 코드 부분을 보면 SoftwareSerial Serial1(2,3)으로 소프트웨어 시리얼로 선언되어 있는 것을 확인할 수 있다.


#include "WiFiEsp.h"


// Emulate Serial1 on pins 6/7 if not present

#ifndef HAVE_HWSERIAL1

#endif

#include "SoftwareSerial.h"

SoftwareSerial Serial1(2, 3); // RX, TX


char ssid[] = "{WIFI SSID}";            // your network SSID (name)

char pass[] = "{WIFI Password}";        // your network password

int status = WL_IDLE_STATUS;     // the Wifi radio's status


char server[] = "arduino.cc";


// Initialize the Ethernet client object

WiFiEspClient client;


void setup()

{

 // initialize serial for debugging

 Serial.begin(9600);

 // initialize serial for ESP module

 Serial1.begin(9600);

 // initialize ESP module

 WiFi.init(&Serial1);


 // check for the presence of the shield

 if (WiFi.status() == WL_NO_SHIELD) {

   Serial.println("WiFi shield not present");

   // don't continue

   while (true);

 }


 // attempt to connect to WiFi network

 while ( status != WL_CONNECTED) {

   Serial.print("Attempting to connect to WPA SSID: ");

   Serial.println(ssid);

   // Connect to WPA/WPA2 network

   status = WiFi.begin(ssid, pass);

 }


 // you're connected now, so print out the data

 Serial.println("You're connected to the network");

 

 printWifiStatus();


 Serial.println();

 Serial.println("Starting connection to server...");

 // if you get a connection, report back via serial

 if (client.connect(server, 80)) {

   Serial.println("Connected to server");

   // Make a HTTP request

   client.println("GET /asciilogo.txt HTTP/1.1");

   client.println("Host: arduino.cc");

   client.println("Connection: close");

   client.println();

 }

}


void loop()

{

 // if there are incoming bytes available

 // from the server, read them and print them

 while (client.available()) {

   char c = client.read();

   Serial.write(c);

 }


 // if the server's disconnected, stop the client

 if (!client.connected()) {

   Serial.println();

   Serial.println("Disconnecting from server...");

   client.stop();


   // do nothing forevermore

   while (true);

 }

}



void printWifiStatus()

{

 // print the SSID of the network you're attached to

 Serial.print("SSID: ");

 Serial.println(WiFi.SSID());


 // print your WiFi shield's IP address

 IPAddress ip = WiFi.localIP();

 Serial.print("IP Address: ");

 Serial.println(ip);


 // print the received signal strength

 long rssi = WiFi.RSSI();

 Serial.print("Signal strength (RSSI):");

 Serial.print(rssi);

 Serial.println(" dBm");

}


접속하고자하는 서버의 주소는

char server[] = "arduino.cc";

에 arduino.cc 로 정의되어 있고


   client.println("GET /asciilogo.txt HTTP/1.1");

   client.println("Host: arduino.cc");

   client.println("Connection: close");

   client.println();


명령어를 이용해서 arduino.cc/asciilogo.txt 파일을 HTTP GET으로 읽어오게 되어있다.

읽어온 응답값을

 while (client.available()) {

   char c = client.read();

   Serial.write(c);

 }


코드를 이용해서 client.read를 통해서 한글자씩 읽어온후 화면에 출력하기 위해서 Seirial.write(c)를 이용하여 한글자씩 콘솔에 출력하였다.

실행 결과는 다음과 같다.



결론

테스트를 하면서 가장 어려웠던 점이 아두이노 우노에서 ESP01 로 소프트웨어 시리얼 통신을 하는 경우 HTTP나 WIFI SDK를 찾기가 어려웠다. 대부분 ESP8266을 MCU로 하는 보드 (예를 들어 nodeMCU, ESP12,Wemo D1/D1 mini)를 베이스로 하는 경우가 많았는데, 그도 그럴것이 개발이나 테스트 용도가 아니라 실제 디바이스로 만들어서 사용하려면 우노 + ESP01 조합은 크기도 크고 연결도 복잡해서 효용성이 없다. 네트워크 통신을 하는 시나리오등은 IOT와 같이 센서 데이타를 얻어서 서버에 전송하는 케이스이기 때문에,  하나의 소형 디바이스로 처리가 가능하다면 하나의 디바이스를 사용하는 것이 훨씬 효율적이다. (가격이나 배터리 용량면등).

다음 글에서는 실제로 센서에서 데이타를 읽어서 HTTP를 이용해서 서버에 전송하는 부분을 고민해보도록 하겠다.

참고자료



ESP01 (ESP8266) 사용하기

프로그래밍/아두이노 | 2018.09.30 00:00 | Posted by 조대협

아두이노 ESP01 모듈 사용하기

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

아두이노 WIFI 모듈

아두이노 WIFI 모듈은 여러가지가 있는데, 아두이노용으로 나온 와이파이 실드의 경우에는 가격이 비싸다. (아두이노에서 WIFI 연결하는 방법은 http://bcho.tistory.com/1280 자료 참고) 그래서 가장 범용적으로 사용되는 칩셋은 ESP8266 칩셋인데, ESP8266 칩셋으로 나온 보드는 여러가지가 있다. ESP01~ESP14 모듈등이 있고, 조금더 사용이 편한 모듈로는 nodemcu와 같은 모듈이 있다.




ESP8266 모듈 시리즈 https://en.wikipedia.org/wiki/ESP8266

오늘 다루는 모듈은 이중에서 가장 저렴한 ESP01 모듈이다.

아두이노에서 이모듈을 사용하기에는 몇가지 넘어야할 산이 있다.

전원

ESP 01은 3V로 동작을 하는데, 전류 소모량이 많다. 그래서 아두이노 보드의 3.3V 단자에 연결하게 되면 전력양이 낮아서 오작동하는 경우가 많고 GND와 전원으로 들어가는 선이 많아서, 아래와 같이 배선이 복잡해진다.


<출처 : https://m.blog.naver.com/roboholic84/221261124179>

통신속도와 펌웨어 업그레이드

그리고 가장 난관중 하나가, 아두이노의 시리얼 통신의 경우 9600bps를 사용하는데, 불행하게도 ESP01의 기본 통신속도는 115200bps로 설정되어 있기 때문에 펌웨어 업그레이드를 통해서 디폴트 통신 속도를 9600 bps로 변경해줘야 한다. 이 과정에 여러 배선 설계를 해야 하고 별도의 펌웨어 업그레이드 프로그램을 설치해야하기 때문에 그 과정이 까다롭다.



<그림 USB to ESP01 연결 아답터>

그래서 아두이노 보드를 거치지 않고 바로 PC USB에서 ESP01로 연결을해서 쉽게 펌웨어를 업데이트할 수 있게 해주는 USB to ESP01 아답터가 있기는 하지만 여전히 펌웨어 업데이트가 필요하다.


ESP01 아답터

그래서 검색을 하다 찾은 아답터가 ESP01 아답터 이다.


이렇게 아답터 위에 ESP01 보드를 설치하면 되는 형태이다. (구매 링크)

https://m.blog.naver.com/roboholic84/221261124179 글에 상세한 소개와 설명이 잘 나와 있는데, 따라해보니 ESP01의 펌웨어 자체가 업그레이드 되어 있어서, 명령어가 동작하지 않는 부분이 있어서 몇가지 수정을 하였다. 포스팅 내용을 참고하여 몇가지 부분을 수정하였다.

배선 작업은 아래와 같다.


대략 빵판에 연결하면 다음과 같은 모습이된다.



레큘레이터가 내장되어 있기 때문에 아두이노 5V에 VCC를 연결하면 되고, RX와 TX를 각각 3번과 2번 포트에 연결한다.

다음에 아래와 같은 코드를 작성하여 실행한다.

#include <SoftwareSerial.h>


SoftwareSerial mySerial(2, 3); // RX, TX


void setup() {

 Serial.begin(9600);

 mySerial.begin(115200);

}


void loop() {

 if (mySerial.available()) {

   Serial.write(mySerial.read());

 }

 if (Serial.available()) {

   mySerial.write(Serial.read());

 }

}


위의 예제는 아두이노 콘솔에서 받은 명령을 ESP01 시리얼 포트로 전송하고 ESP01 에서 나온 결과값을 아두이노 콘솔에 출력하도록 하는 코드이다. ESP01의 디폴트 통신속도가 115200이기 때문에 mySerial.begin에서 통신 속도를 115200으로 설정하였다. (추후 바꿀것이다.)


를 실행해서 AT 명령을 실행해보자. 연결이 제대로 되었으면 아래와 그림과 같이 OK 응답이 오는 것을 볼 수 있다.



다음 ESP01 보드의 통신 속도를 변경해보자,원본 문서에는 AT+CIOBAUD=9600 명령으로 변경하도록 되어 있는데, 이 명령은 최신 펌웨어에서는 동작하지 않고 AT+IPR이라는 명령을 사용해야 하는데, 이 명령은 연결되어 있는 동안만 통신 속도를 변경하고 다시 ESP01 디바이스를 뺐다가 다시 끼면 원래 통신속도로 돌아간다.

ESP01의 통신속도를 영구적으로 바꿔줄 수 있는 명령이 있는데, AT_UART_DEF라는 명령을 사용하면 된다. (참고 : https://www.esp8266.com/viewtopic.php?f=13&t=718)

사용법 : AT+ UART_DEF=<baudrate>,<databits>,<stopbits>,<parity>,<flow control>


통신속도를 9600으로 영구적으로 변경하기 위해서는 아래 명령을 수행한다.

AT+UART_DEF=9600,8,1,0,0



통신 속도를 9600으로 변경하였기 때문에 코드상에서도 ESP01로 통신하는 속도를 9600으로 변경해줘야 한다.

 mySerial.begin(115200);

부분을

 mySerial.begin(9600);

으로 변경하여 실행하자


WIFI 연결 테스트

네트워크 모드

ESP8266은 네트워크 연결에 대해 3가지 모드를 제공한다.

  • 1 : Stand alone

  • 2 : AP

  • 3 : AP + Standalone

Stand alone 모드는 클라이언트로 작동하는 모드로 AP에 붙어서 네트워크 통신을 할 수 있다.

AP 모드는 ESP8266이 서버가 되는 모드로 다른 단말이 ESP8266에 Wifi로 연결될 수 있게 한다. 그리고 AP+Stand alone 모드는 서버와 클라이언트를 동시에 지원하는 모드이다. 여기서는 1번 AP+Standalone 모드를 사용하도록 하겠다.

모드 전환은 AT+CWMODE={모드 번호}로 가능하다


WIFI 연결하기

클라이언트 모드로 연결을 하였으면 WIFI에 연결해보자.

WIFI 목록을 찾는 명령은 AT+CWLAP 이다. 명령을 실행하면 아래 그림과 같이 연결 가능한 WIFI 목록이 출력된다.


다음 AT+CWJAP=”SSID”,”비밀번호" 명령을 이용하여 연결하고자 하는 WIFI 네트워크에 연결이 가능하다. 여기서는 U+NetC070 네트워크에 연결해보도록 하겠다

AT+CWJAP="U+NetC070","WIFI비밀번호"


명령을 실행하면 아래와 같이 WIFI에 연결이 된것을 확인할 수 있다.



연결이 완료 되었으면

AT+CIFSR

명령을 이용하면 연결된 IP 주소를 읽어낼 수 있다.


이렇게 연결이 된 상태에서는 아두이노의 전원을 껐다가 켜도 다시 같은 네트워크로 자동으로 붙기 때문에, 연결을 명시적으로 끊으려면

AT+CWQAP

명령을 이용하여 명시적으로 연결을 끊어줘야 한다.

다음글에서는 ESP01 모듈을 이용하여 서버와 HTTP 통신을 하는 방법에 대해서 알아보도록 한다.




구글 클라우드 해커톤 세션으로 진행한 강의 내용입니다

50분 정도 인데, 짧게 쿠버네티스에 대한 설명과, 마이크로서비스에 대한 설명

그리고 쿠버네티스 에코 시스템인 Spinnaker, Istio, KNative 등에 대해서 설명합니다



쿠버네티스 #19

보안 4/4 - Pod Security Policy

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



SecurityContext가 컨테이너나 Pod의 보안 기능을 정의 하는 것이라면, Pod Security Policy (이하 PSP)는 보안 기능에 대한 정책을 정의 하는 것이다.

예를 들어, 정책으로 Pod를 생성할때는 반드시 root 사용자를 사용하지 못하도록 강제한다던지, Privileged 모드를 사용못하도록 강제할 수 있다. 현재는 (2018년9월1일) 베타 상태이기 때문에 다소의 기능 변경이 있을 수 있음을 염두하고 사용하도록 하자.

개념

개념이 복잡하기 때문에 먼저 기본적인 개념을 이해한 후에, 각 상세를 살펴보도록 하자.

먼저 아래 그림을 보자 PSP는 생성후에, 사용자에게 지정이 된다.

그리고 Pod를 생성할때, Pod의 보안 요건을 SecurityContext를 이용해서 Pod 설정에 정의한다.

Pod를 생성하려고 할때, 생성자(사용자)의 PSP를 레퍼런스 하는데, Pod의 보안 요건이 사용자에게 정의되어 있는 PSP 요건을 만족하면, Pod가 생성된다.



반대로, Pod를 생성할때, Pod의 보안 요건 (SecurityContext)가 Pod를 생성하고자하는 사용자의 PSP요건을 만족하지 않으면, Pod 생성이 거부된다. 아래 그림은 사용자의 PSP에서 Privileged 모드를 사용할 수 없도록 설정하였으나, Pod를 생성할때 Privileged 모드를 Pod 가 사용할 수 있도록 설정하였기 때문에, Pod를 생성에 실패하는 흐름이다.




Pod Security Policy

Pod Security Policy는 Security Context와 달리 클러스터 리소스 (Cluster Resource)이다.

즉 적용하는 순간 클러스터 전체에 적용이 된다는 이야기이다.


정책 종류

Pod Security Policy를 통해서 통제할 수 있는 정책은 다음과 같다.

(출처 https://kubernetes.io/docs/concepts/policy/pod-security-policy/) 자세한 내용은 원본 출처를 참고하기 바란다.


Control Aspect

Field Names

Running of privileged containers

privileged

Usage of host namespaces

hostPID, hostIPC

Usage of host networking and ports

hostNetwork, hostPorts

Usage of volume types

volumes

Usage of the host filesystem

allowedHostPaths

White list of Flexvolume drivers

allowedFlexVolumes

Allocating an FSGroup that owns the pod’s volumes

fsGroup

Requiring the use of a read only root file system

readOnlyRootFilesystem

The user and group IDs of the container

runAsUser, supplementalGroups

Restricting escalation to root privileges

allowPrivilegeEscalation, defaultAllowPrivilegeEscalation

Linux capabilities

defaultAddCapabilities, requiredDropCapabilities, allowedCapabilities

The SELinux context of the container

seLinux

The AppArmor profile used by containers

annotations

The seccomp profile used by containers

annotations

The sysctl profile used by containers

annotations



포맷

PSP의 포맷을 이해하기 위해서 아래 예제를 보자

apiVersion: extensions/v1beta1

kind: PodSecurityPolicy

metadata:

 name: nonroot-psp

spec:

 seLinux:

   rule: RunAsAny

 supplementalGroups:

   rule: RunAsAny

 runAsUser:

   rule: MustRunAsNonRoot

 fsGroup:

   rule: RunAsAny

 volumes:

 - '*'


nonroot-psp 라는 이름으로 PSP를 정의하였고, seLinux,supplementalGroup,fsGroup과 volumes(디스크)에 대한 권한은 모두 허용하였다. runAsUser에 rule (규칙)을 MustRunAsNonRoot로 지정해서, 이 정책을 적용 받은 사용자는 Pod를 생성할때 Pod가 반드시 root 사용자가 아닌 다른 사용자를 지정하도록 정의했다.

PSP 사용자 적용

PSP 를 정의하고 실행한다고 해도, 실제로 적용되지 않는다. PSP를 적용하기 위해서는 생성한 PSP를 RBAC을 이용하여 ClusterRole을 만들고, 이 ClusterRole을 사용자에게 부여해야 실제로 정책이 적용되기 시작한다. 사용자에게 PSP를 적용하는 부분은 뒤의 예제에서 살펴보자

이때 주의할점은 사용자의 정의인데, 쉽게 생각하면 사용자를 사람으로만 생각할 수 있는데, 쿠버네티스의 사용자는 사람이 될 수 도 있지만 서비스 어카운트 (Service account)가 될 수 도 있다.

쿠버네티스에서 Pod를 생성하는 주체는 사용자가 kubectl 등으로 Pod를 직접생성할 경우, 사람이 사용자가 되지만, 대부분의 경우 Pod의 생성과 관리는 Deployment나 ReplicaSet과 같은 컨트롤러를 이용하기 때문에, 이 경우에는 컨트롤러들이 사용하는 서비스 어카운트가 사용자가 되는 경우가 많다.

그래서, PSP를 적용하는 대상은 일반 사용자가 될 수 도 있지만 서비스 어카운트에 PSP를 적용해야 하는 경우가 많다는 것을 반드시 기억해야 한다.

PSP 활성화

PSP는 쿠버네티스 클러스터에 디폴트로는 비활성화 되어 있다. PSP 기능을 사용하기 위해서는 이를 활성화 해야 하는데, PSP는 admission controller에 의해서 컨트롤 된다.

구글 클라우드

구글 클라우드에서 PSP를 활성화 하는 방법은 아래와 같이 gcloud 명령을 이용하면 된다.


%gcloud beta container clusters update {쿠버네티스 클러스터 이름} --enable-pod-security-policy --zone={클러스터가 생성된 구글 클라우드 존}


만약에 활성화된 PSP 기능을 비활성화 하고 싶으면 아래와 같이 gcloud 에서 --no-enable-pod-security-policy  옵션을 사용하면 된다.


gcloud beta container clusters update {쿠버네티스 클러스터 이름}  --no-enable-pod-security-policy --zone={클러스터가 생성된 구글 클라우드 존}

Minikube

minikube start --extra-config=apiserver.GenericServerRunOptions.AdmissionControl=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds,PodSecurityPolicy


주의할점은 PSP 기능이 활성화된후에, PSP가 적용되지 않은 사용자(사람과, 서비스어카운트 모두)의 경우에는 Pod를 생성할 수 없기 때문에, 기존에 잘 생성되던 Pod가 갑자기 생성되지 않는 경우가 많기 때문에, 반드시 기능을 활성화하기 전에 반드시, 사용자마다 적절한 PSP를 생성해서 적용하기 바란다. (PSP기능을 활성화하지 않더라도 기본적으로 PSP 정의및, PSP를 사용자에게 적용하는 것은 가능하다.)

예제

개념에 대한 이해가 끝났으면 이제 실제 예제를 통해서 어떻게 PSP를 생성 및 적용하는지를 알아보도록 하자. 예제는 다음 순서로 진행하도록 한다.

  1. PSP 정의 : Root 권한을 사용이 불가능한 PSP를 생성한다.

  2. 서비스 어카운트 생성 : PSP를 생성할 서비스 어카운트를 생성한다. Pod를 바로 생성하는 것이 아니라 Deployment를 통해서 생성할것이기 때문에 Deployment에서 이 서비스 어카운트를 사용할것이다.

  3. ClusterRole 생성 : 다음 1에서 만든 PSP를 2에서 만든 서비스 어카운트에 적용하기 위해서, PSP를 가지고 있는 ClusterRole을 생성한다.

  4. ClusterRoleBinding을 이용하여 서비스 어카운트에 PSP 적용 : 3에서 만든 ClusterRole을 2에서 만든 서비스 어카운트에 적용한다.

  5. Admission controller 활성화 : PSP를 사용하기 위해서 Admission controller를 활성화 한다.

  6. Pod 정의 및 생성 : 2에서 만든 서비스 어카운트를 이용하여 Deployment 를 정의한다.

  7. 테스트 : 테스트를 위해서, root user를 사용하는 deployment와, root user를 사용하지 않는 deployment 두개를 각각 생성해서 psp 가 제대로 적용되는지를 확인한다.

PSP 정의

PSP를 정의해보자. 아래와 같이 nonroot-psp.yaml 을 작성한다. 이 PSP는 runAsUser에서 MustRunAsNotRoot 규칙을 추가해서, Root 권한으로 컨테이너가 돌지 않도록 하는 정책이다.


# nonroot-psp.yaml

apiVersion: extensions/v1beta1

kind: PodSecurityPolicy

metadata:

 name: nonroot-psp

spec:

 seLinux:

   rule: RunAsAny

 supplementalGroups:

   rule: RunAsAny

 runAsUser:

   rule: MustRunAsNonRoot

 fsGroup:

   rule: RunAsAny

 volumes:

 - '*'


파일을 nonroot-psp.yaml 파일로 저장한후에,

%kubectl create -f nonroot-psp.yaml

명령어를 이용하여 PSP를 생성한후에,

%kubectl get psp

명령을 이용하여, PSP가 생성된것을 확인하자




서비스 어카운트 생성

서비스 어카운트 생성을 위해서 아래 yaml 파일을 작성하고, 서비스 어카운트를 생성하여 확인하자


#nonroot-sa.yaml

apiVersion: v1

kind: ServiceAccount

metadata:

 name: nonroot-sa



ClusterRole 생성 및 적용

서비스 어카운트를 생성하였으면, 앞에 만든 PSP nonroot-psp 를 사용하는 ClusterRole nonroot-clusterrole을 생성하고, 이 롤을 nonroot-clusterrole-bindings를 이용하여, 앞서 만든 서비스 어카운트 nonroot-sa 에 연결한다.


아래와 같이 ClusterRole을 생성하는데, resouces 타입을 podsecuritypolicies 로 정의하고, 리소스 이름은 앞에서 생성한 PSP인 nonroot-psp로 지정한다. 그리고, 이 psp를 사용하기 위해서 verb는 “use”로 지정한다

#nonroot-clusterbinding.yaml

apiVersion: rbac.authorization.k8s.io/v1

kind: ClusterRole

metadata:

 name: nonroot-clusterrole

rules:

- apiGroups:

 - policy

 resources:

 - podsecuritypolicies

 resourceNames:

 - nonroot-psp

 verbs:

 - use


%kubectl create -f nonroot-clusterrole.yaml

명령어를 이용하여 위의 ClusterRole을 생성한후에, 이 ClusterRole을 서비스 어카운트 nonroot-sa 에 적용하자.

아래와 같이 nonroot-clusterrolebinding.yaml 를 생성한후,


#nonroot-clusterrolebinding.yaml

apiVersion: rbac.authorization.k8s.io/v1

kind: ClusterRoleBinding

metadata:

 name: nonroot-clusterrole-bindings

subjects:

- kind: ServiceAccount

 name: sa-nonroot

 namespace: default

roleRef:

 apiGroup: rbac.authorization.k8s.io

 kind: ClusterRole

 name: nonroot-clusterrole


%kubectl create -f nonroot-clusterrolebinding.yaml

명령어를 이용하여 ClusterRole nonroot-clusterrole을 서비스 어카운트 sa-nonroot에 적용한다.

도커 컨테이너 생성

이제 PSP가 생성되었고, 이 PSP를 사용하는 서비스 어카운트 nonroot-sa 가 완성되었으면, 이를 실제로 배포에 적용해보자. 배포에 앞서서 컨테이너 이미지를 만든다.

아래는 Docker 파일인데, 앞의 보안 컨텍스트 설명때 사용한 컨테이너와 동일하다.


#Dockerfile

FROM node:carbon

EXPOSE 8080

RUN groupadd -r -g 2001 appuser && useradd -r -u 1001 -g appuser appuser

RUN mkdir /home/appuser && chown appuser /home/appuser

USER appuser

WORKDIR /home/appuser

COPY --chown=appuser:appuser server.js .

CMD node server.js > /home/appuser/log.out

생성된 도커이미지를 gcr.io/terrycho-sandbox/nonroot-containe:v1 이름으로 docker push 명령을 이용해서  컨테이너 레지스트리에 등록한다.

PSP 기능 활성화

이미지까지 준비가 되었으면, 이제 Pod를 생성할 모든 준비가 되었는데, PSP를 사용하려면, 쿠버네티스 클러스터에서 PSP 기능을 활성화 해야 한다.

다음 명령어를 이용해서 PSP를 활성화한다.

%gcloud beta container clusters update {쿠버네티스 클러스터 이름} --enable-pod-security-policy --zone={클러스터가 생성된 구글 클라우드 존}


아래 그림과 같이 PSP 기능이 활성화 되는 것을 확인한다.


Deployment 생성

기능 활성화가 끝났으면, 이제 Pod를 deploy해보자.

아래는 nonroot-deploy.yaml 파일이다.


#nonroot-deploy.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

 name: nonroot-deploy

spec:

 replicas: 3

 selector:

   matchLabels:

     app: nonroot

 template:

   metadata:

     name: nonroot-pod

     labels:

       app: nonroot

   spec:

     serviceAccountName: nonroot-sa

     securityContext:

       runAsUser: 1001

       fsGroup: 2001

     containers:

     - name: nonroot

       image: gcr.io/terrycho-sandbox/security-context:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080


우리가 nonroot-psp를 사용하기 위해서, 이 psp가 정의된 서비스 어카운트 nonroot-sa를 사용하도록 하였다. 그래고 nonroot-psp에 정의한데로, 컨테이너가 root 권한으로 돌지 않도록 securityContext에 사용자 ID를 1001번으로 지정하였다.

%kubectl create -f nonroot-deploy.yaml

을 실행한후,

%kubectl get deploy 명령어를 실행해보면 아래와 같이 3개의 Pod가 생성된것을 확인할 수 있다.


보안 정책에 위배되는 Deployment 생성

이번에는 PSP 위반으로, Pod 가 생성되지 않는 테스트를 해보자.

아래와 같이 root-deploy.yaml 이라는 이름으로, Deployment 스크립트를 작성하자.


#root-deploy.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

 name: root-deploy

spec:

 replicas: 3

 selector:

   matchLabels:

     app: root

 template:

   metadata:

     name: root-pod

     labels:

       app: root

   spec:

     serviceAccountName: nonroot-sa

     containers:

     - name: root

       image: gcr.io/terrycho-sandbox/nonroot-containe:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080


이 스크립트는 앞에서 작성한 nonroot-deploy.yaml 과 거의 유사하지만 Security Context에서 사용자 ID를 지정하는 부분이 없기 때문에, 디폴트로 root로 컨테이너가 기동된다. 그래서 PSP에 위반되게된다.


%kubectl create -f root-deploy.yaml

을 실행하면 결과가 아래와 같다.



맨 아래 root-deploy-7895f57f4를 보면, Current 가 0으로 Pod가 하나도 기동되지 않았음을 확인할 수 있다.

원인을 파악하기 위해서 Pod를 만드는 ReplicaSet을 찾아보자

%kubectl get rs

명령을 아래와 같이 ReplicaSet 리스트를 얻을 수 있다.

%kubectl describe rs root-deploy-7895f57f4

명령을 실행해서 ReplicaSet의 디테일과 로그를 확인해보면 다음과 같다.



그림과 같이 Pod 생성이 정책 위반으로 인해서 실패한것을 확인할 수 있다.



쿠버네티스 #16


보안 1/4 - 사용자 계정 인증 및 권한 인가

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


이번글 부터는 몇회에 걸쳐 쿠버네티스 계쩡 인증,인가, 네트워크등 보안에 관련된 부분을 알아보도록 하겠다.

모든 시스템이 그렇듯이, 쿠버네티스 역시 보안이 매우 중요하다. 쿠버네티스는 보안에 관련된 여러가지 기능을 제공하는데, 각각에 대해서 살펴 보도록 하자

사용자 인증 및 권한 관리

인증과 인가 (Authentication & Authorization)

먼저 인증과 인가에 대한 개념에 대해서 이해 하자



인증(Authentication)은 사용자가 누구인지를 식별하는 것이다. 흔히 생각하는 사용자 로그인을 생각하면 된다.  인가는 인증된 사용자가 해당 기능을 실행할 수 있는 권한이 있는지를 체크하는 기능이다.

인증 (Authentication)

쿠버네티스는 계정 체계를 관리함에 있어서 사람이 사용하는 사용자 어카운트와, 시스템이 사용하는 서비스 어카운트 두가지 개념을 제공한다.

사용자 어카운트

사용자 어카운트는 우리가 일반적으로 생각하는 사용자 아이디의 개념이다.

쿠버네티스는 자체적으로 사용자 계정을 관리하고 이를 인증(Authenticate)하는 시스템을 가지고 있지 않다. 반드시 별도의 외부 계정 시스템을 사용해야 하며, 계정 시스템 연동을 위해서 OAuth나 Webhook가 같은 계정 연동 방식을 지원한다.

서비스 어카운트

서비스 어카운트가 다소 낮설 수 있는데, 예를 들어 클라이언트가 쿠버네티스 API를 호출하거나, 콘솔이나 기타 클라이언트가 쿠버네티스 API를 접근하고자 할때, 이는 실제 사람인 사용자가 아니라 시스템이 된다. 그래서, 쿠버네티스에서는 이를 일반 사용자와 분리해서 관리하는데 이를 서비스 어카운트 (service account)라고 한다.

서비스 어카운트를 생성하는 방법은 간단하다.

%kubectl create sa {서비스 어카운트명}

을 실행하면 된다. 아래는 foo 라는 이름으로 서비스 어카운트를 생성하는 예이다.



인증 방법

그러면 계정이 있을때, 이 계정을 이용해서 쿠버네티스의 API에 어떻게 접근을 할까? 쿠버네티스는 사용자 인증을 위해서 여러가지 메커니즘을 제공한다.

용도에 따라서 다양한 인증 방식을 제공한다.


  • Basic HTTP Auth

  • Access token via HTTP Header

  • Client cert

  • Custom made


Basic HTTP Auth는 HTTP 요청에 사용자 아이디와 비밀번호를 실어 보내서 인증하는 방식인데, 아이디와 비밀번호가 네트워크를 통해서 매번 전송되기 때문에 그다지 권장하지 않는 방법이다.

Access token via HTTP Header는 일반적인 REST API 인증에 많이 사용되는 방식인데, 사용자 인증 후에, 사용자에 부여된 API TOKEN을 HTTP Header에 실어서 보내는 방식이다.

Client cert는 클라이언트의 식별을 인증서 (Certification)을 이용해서 인증하는 방식이다. 한국으로 보자면 인터넷 뱅킹의 공인 인증서와 같은 방식으로 생각하면 된다. 보안성은 가장 높으나, 인증서 관리에 추가적인 노력이 필요하다.

그러면 쉽지만 유용하게 사용할 수 있는 Bearer token 방식의 인증 방식을 살펴보도록 하자

Bearer token을 사용하는 방법

인증 메커니즘 중에서 상대적으로 가장 간단한 방법은 API 토큰을 HTTP Header에 넣는 Bearer token 인증 방식이 있다.

서비스 어카운트의 경우에는 인증 토큰 정보를 secret에 저장을 한다. 이 토큰 문자열을 가지고, HTTP 헤더에 “Authorization: Bearer {토큰문자열}로 넣고 호출하면 이 토큰을 이용해서 쿠버네티스는 API 호출에 대한 인증을 수행한다.


서비스 어카운트에서 토큰 문자열을 가지고 오는 방법은

%kubectl describe sa {service account 이름}

을 실행하면 아래와 같이 Token 항목에 토큰을 저장하고 있는 secret 이름이 나온다.


위의 그림에서는 foo-token-zvnzz 이다. 이 이름으로 secret을 조회해보면,

%kubectl describe secret {시크릿명}

명령을 실행하면 아래와 같이 token이라는 항목에, 토큰이 문자열로 출력이 된다.



이 토큰을 HTTP Header 에 “Authorization: Bearer {토큰문자열}” 식으로 넣고 호출하면 된다.


간단한 스크립트를 통해서 API를 호출하는 것을 테스트 해보자

% APISERVER=$(kubectl config view | grep server | cut -f 2- -d ":" | tr -d " ")
명령을 수행하면 환경 변수 APISERVER에 현재 쿠버네티스 클러스터의 API SERVER IP가 저장된다.


다음 APISERVER의 주소를 알았으니

% curl $APISERVER/api

명령을 이용해서 HTTP GET의 /api를 호출해보자. 호출을 하면 SSL 인증서에 대한 인증 에러가 발생한다.


이는 API를 호출할때 인증에 필요한 정보를 기재하지 않았기 때문에, 디폴트로 Client cert를 이용한 인증을 시도하게 되고, 인증서를 지정하지 않았기 때문에 에러가 나게 되는것이다.


그러면 인증 정보를 제대로 지정하기 위해서 서비스 어카운트 default의 토큰을 얻어서 호출해보도록 하자.

다음 스크립트는 서비스 어카운트 default의 secret에서 토큰을 추출해서 저장하는 스크립트이다.

%TOKEN=$(kubectl describe secret $(kubectl get secrets | grep default | cut -f1 -d ' ') | grep -E '^token' | cut -f2 -d':' | tr -d '\t')

스크립트를 실행한후 TOKEN의 내용을 찍어 보면 아래와 같이 API TOKEN이 저장된것을 확인할 수 있다.




다음 이 토큰을 이용해서 API를 호출하면 된다.

%curl https://35.189.143.107/api --header "Authorization: Bearer $TOKEN" --insecure




Kubectl proxy를 이용한 API 호출

앞에서는 HTTP Header에 토큰을 직접 입력하는 방식을 사용했지만, 이렇게 사용하는 경우는 드물다. curl을 이용해서 호출할 경우에는 kubectl proxy 명령어를 이용해서 proxy를 설정하고 proxy로 API URL을 호출하면, 자동으로 이 Proxy가 현재 클라이언트의 kubeconfig file에 저장되어 있는 Credential (인증 정보)를 채워서 자동으로 보내준다.


%kubectl proxy --port=8080

을 실행하게 되면, localhost:8080을 프록시로 하여 쿠버네티스 API서버로 요청을 자동으로 포워딩 해준다.


그리고 curl localhost:8080/api 를 호출하면 {쿠버네티스 API Server}/api 를 호출해주게 된다.




SDK를 이용한 호출

일반적으로 간단한 테스트가 아닌 이상, curl 을 이용해서 직접 API를 호출하는 경우는 드물고, SDK를 사용하게 된다.  쿠버네티스에는 Go/Python/Java/Javascript 등 다양한 프로그래밍 언어를 지원하는 SDK가 있다.

https://kubernetes.io/docs/reference/using-api/client-libraries/#officially-supported-kubernetes-client-libraries


이들 SDK 역시, kubectl proxy 처럼, 로컬의 kubeconfig file의 Credential 정보를 이용해서 API를 인증하고 호출 한다.

권한 관리 (Authorization)

계정 체계와 인증에 대한 이해가 끝났으면, 이번에는 계정 권한에 대해서 알아보자. 쿠버네티스의 권한 처리 체계는 기본적으로 역할기반의 권한 인가 체계를 가지고 있다. 이를 RBAC (Role based access control)이라고 한다.


권한 구조를 도식화 해보면 다음과 같이 표현할 수 있다.


  • 사용자의 계정은 개개별 사용자인 user, 그리고 그 사용자들의 그룹은 user group, 마지막으로 시스템의 계정을 정의하는 service account로 정의된다.

  • 권한은 Role이라는 개념으로 정의가 되는데, 이 Role에는 각각의 리소스에 대한 권한이 정의된다. 예를 들어 pod 정보에대한 create/list/delete등을 정의할 수 있다. 이렇게

  • 이렇게 정의된 Role은 계정과 RoleBinding 이라는 정의를 통해서, 계정과 연결이 된다.


예제를 살펴보자, 아래는 Role을 정의한 yaml 파일이다.

pod-reader라는 Role을 정의하였고, pods에 대한 get/watch/list를 실행할 수 있는 권한을 정의하였다.



다음 이 Role을 사용자에게 부여하기 위해서 RoleBinding 설정을 아래와 같이 정의하자.

아래 Role-Binding은 read-pods라는 이름으로 jane이라는 user에서 Role을 연결하였고, 앞에서 정의한 pod-reader를 연결하도록 정의하였다.




이 예제를 그림으로 표현하면 다음과 같다.



Role vs ClusterRole

Role은 적용 범위에 따라 Cluster Role과 일반 Role로 분리 된다.

Role의 경우 특정 네임스페이스내의 리소스에 대한 권한을 정의할 수 있다.

반면 ClusterRole의 경우, Cluster 전체에 걸쳐서 권한을 정의할 수 있다는 차이가 있다.

또한 ClusterRole의 경우에는 여러 네임스페이스에 걸쳐 있는 nodes 와 같은 리소스스나 /heathz와 같이 리소스 타입이 아닌 자원에 대해서도 권한을 정의할 수 있다.

Role과 ClusterRole은 각각 RoleBinding과 ClusterRoleBinding 을 통해서 사용자에게 적용된다.

Predefined Role

쿠버네티스에는 편의를 위해서 미리 정해진 롤이 있다.


Default ClusterRole

Default ClusterRoleBinding

Description

cluster-admin

system:masters group

쿠버네티스 클러스터에 대해서 수퍼사용자 권한을 부여한다.
ClusterRoleBinding을 이용해서 롤을 연결할 경우에는 모든 네임스페이스와 모든 리소스에 대한 권한을 부여한다. RoleBinding을 이용하여 롤을 부여하는 경우에는 해당 네임 스페이스에 있는 리소스에 대한 모든 컨트롤 권한을 부여한다.

admin

None

관리자 권한의 억세스를제공한다. RoleBinding을 이용한 경우에는 해당 네임스페이스에 대한 대부분의 리소스에 대한 억세스를 제공한다.  새로운 롤을 정의하고 RoleBinding을 정의하는 권한을 포함하지만, resource quota에 대한 조정 기능은 가지지 않는다.

edit

None

네임스페이스내의 객체를 읽고 쓰는 기능은 가지지만, role이나 rolebinding을 쓰거나 수정하는 역할은 제외된다.

view

None

해당 네임스페이스내의 객체에 대한 읽기기능을 갔는다. role이나 rolebinding을 조회하는 권한은 가지고 있지 않다.


미리 정해진 롤에 대한 자세한 정보는  https://kubernetes.io/docs/reference/access-authn-authz/rbac/

를 참고하기 바란다.

권한 관리 예제

이해를 돕기 위해서 간단한 예제를 하나 테스트 해보자. 작성하는 예제는 Pod를 하나 생성해서 curl 명령으로 API를 호출하여, 해당 클러스터의 Pod 리스트를 출력하는 예제를 만들어보겠다.

Pod가 생성될때는 default 서비스 어카운트가 할당이 되는데, 이 서비스 어카운트는 클러스터의 정보를 호출할 수 있는 권한을 가지고 있지 않다. 쿠버네티스에 미리 정의된 ClusterRole중에 view 라는 롤은 클러스터의 대부분의 정보를 조회할 수 있는 권한을 가지고 있다.

이 롤을 sa-viewer 라는 서비스 어카운트를 생성한 후에, 이 서비스 어카운트에 ClusterRole view를 할당한후, 이 서비스 어카운트를 만들고자 하는 Pod에 적용하도록 하겠다.


apiVersion: v1

kind: ServiceAccount

metadata:

 name: sa-viewer

---

kind: ClusterRoleBinding

apiVersion: rbac.authorization.k8s.io/v1

metadata:

 name: default-view

subjects:

- kind: ServiceAccount

 name: sa-viewer

 namespace: default

roleRef:

 kind: ClusterRole

 name: view

 apiGroup: rbac.authorization.k8s.io


먼저 위와 같이 sa-viewer 라는 서비스 어카운트를 생성한후, ClusterRoleBiniding 을 이용하여, default-view라는 ClusterRolebinding 을 생성하고, sa-viewer 서비스 어카운트에, view 롤을 할당하였다.


다음 Pod를 생성하는데, 아래와 같이 앞에서 생성한 서비스 어카운트 sa-viewer를 할당한다.

apiVersion: v1

kind: Pod

metadata:

 name: pod-reader

spec:

 serviceAccountName: sa-viewer

 containers:

 - name: pod-reader

   image: gcr.io/terrycho-sandbox/pod-reader:v1

   ports:

   - containerPort: 8080


Pod 가 생성된 후에, kubectl exec 명령을 이용하여 해당 컨테이너에 로그인해보자

% kubectl exec -it pod-reader -- /bin/bash


로그인 후에 아래 명령어를 실행해보자


$ CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

$ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

$ curl --cacert $CA_CERT -H "Authorization: Bearer $TOKEN" "https://35.200.91.132/api/v1/pods/"


CA_CERT는 API를 HTTPS로 호출하기 위해서 인증서를 저장한 파일의 위치를 지정하는 것이다. Pod의 경우에는 일반적으로 /var/run/secrets/kubernetes.io/serviceaccount/ca.crt  디렉토리에 인증서가 자동으로 설치 된다. 다음은 API TOKEN을 얻기 위해서 TOKEN 값을 가지고 온다. TOKEN은 cat /var/run/secrets/kubernetes.io/serviceaccount/token 에 디폴트로 저장이 된다.

다음 curl 명령으로 https:{API SERVER}/api/v1/pods 를 호출하면 클러스터의 Pod 리스트를 다음 그림과 같이 리턴한다.


\



사용자 관리에 있어서, 계정에 대한 정의와 권한 정의 그리고 권한의 부여는 중요한 기능이기 때문에, 개념을 잘 잡아놓도록하자.


쿠버네티스 #15

모니터링 3/3 구글 스택드라이버를 이용한 모니터링

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



구글 클라우드 쿠버네티스 스택드라이버 모니터링

쿠버네티스 모니터링 시스템을 구축하는 다른 방법으로는 클라우드 서비스를 사용하는 방법이 있다. 그중에서 구글 클라우드에서 제공하는 스택 드라이버 쿠버네티스 모니터링에 대해서 소개하고자한다.

https://cloud.google.com/monitoring/kubernetes-engine/


현재는 베타 상태로, 구글 클라우드 쿠버네티스 서비스 (GKE)에서만 지원이 되며, 쿠버네티스 버전 1.10.2 와 1.11.0 (또는 그 상위버전)에서만 지원이 되고, 모니터링 뿐 아니라, 쿠버네티스 서비스에 대한 로깅을 스택드라이버 로깅 서비스를 이용해서 함께 제공한다.


스택드라이버 쿠버네티스 모니터링을 설정하는 방법은 간단하다. 쿠버네티스 클러스터를 설정할때, 아래 그림과 같이 Additional features 항목에서 “Try the new Stackdriver beta monitoring and Logging experience” 항목을 체크하면 된다.



클러스터를 생성한 후에, 구글 클라우드 콘솔에서 Monitoring 메뉴를 선택한 후에



스택드라이버 메뉴에서 Resources 메뉴에서 아래 그림과 같이 Kubernetes 메뉴를 선택하면 쿠버네티스 모니터링 내용을 볼 수 있다.



모니터링 구조

스택드라이버 쿠버네티스 모니터링의 가장 큰 장점 중의 하나는 단순한 단일 뷰를 통해서 대부분의 리소스 모니터링 과 이벤트에 대한 모니터링이 가능하다는 것이다.

아래 그림이 스택드라이버 모니터링 화면인데, “2”라고 표시된 부분이 시간에 따른 이벤트이다. 장애등이 발생하였을 경우 아래 그림과 같이 붉은 색으로 표현되고, 3 부분을 보면, 여러가지 뷰 (계층 구조)로 각 자원들을 모니터링할 수 있다. 장애가 난 부분이 붉은 색으로 표시되는 것을 확인할 수 있다.



<출처 : https://cloud.google.com/monitoring/kubernetes-engine/observing >


Timeline에 Incident가 붉은 색으로 표시된 경우 상세 정보를 볼 수 있는데, Timeline에서 붉은 색으로 표시된 부분을 누르면 아래 그림과 같이 디테일 이벤트 카드가 나온다. 이 카드를 통해서 메모리,CPU 등 이벤트에 대한 상세 내용을 확인할 수 있다.



<출처 : https://cloud.google.com/monitoring/kubernetes-engine/observing >


반대로 정상적인 경우에는 아래 그림과 같이 이벤트 부분에 아무것도 나타나지 않고, 모든 자원이 녹색 동그라미로 표시되어 있는 것을 확인할 수 있다.


개념 구조

쿠버네티스 모니터링중에 어려운 점중의 하나는 어떤 계층 구조로 자원을 모니터링 하는가 인데, 이런점을 해결하기 위해서 구글 스택드라이버 쿠버네티스 모니터링은 3가지 계층 구조에 따른 모니터링을 지원한다. 모니터링 화면을 보면 아래와 같이 Infrastructure, Workloads, Services 와 같이 세가지 탭이 나오는 것을 볼 수 있다.



어떤 관점에서 클러스터링을 모니터링할것인가인데,

  • Infrastructure : 하드웨어 자원 즉, node를 기준으로 하는 뷰로,  Cluster > Node > Pod > Container 의 계층 구조로 모니터링을 제공한다.

  • Workloads : 워크로드, 즉 Deployment를 중심으로 하는 뷰로 Cluster > Namespace > Workload (Deployment) > Pod > Container 순서의 계층 구조로 모니터링을 제공한다.

  • Services : 애플리케이션 즉 Service 를 중심으로 하는 뷰로 Cluster > Namespace > Service > Pod > Container 계층 순서로 뷰를 제공한다.

Alert 에 대한 상세 정보

각 계층 뷰에서 리소스가 문제가 있을 경우에는 앞의 동그라미가 붉은색으로 표시가 되는데,  해당 버튼을 누르게 되면, Alert 에 대한 상세 정보 카드가 떠서, 아래 그림과 같이 이벤트에 대한 상세 정보를 확인할 수 있다.


<출처 : https://cloud.google.com/monitoring/kubernetes-engine/observing >

결론

지금까지 간단하게 쿠버네티스에 대한 모니터링과 로깅에 대해서 알아보았다. 프로메테우스나 그라파나와 같은 최신 기술을 써서 멋진 대쉬 보드를 만드는 것도 중요하지만 모니터링과 로깅은 시스템을 안정적으로 운영하고 장애전에 그 전조를 파악해서 대응하고, 장애 발생시에는 해결과 향후 예방을 위한 분석 및 개선 활동이 일어나야 한다. 이를 위해서 모니터링과 로깅은 어디까지나 도구일 뿐이고, 어떤 지표를 모니터링 할것인지 (SLI : Service Level Indicator), 지표의 어느값까지를 시스템 운영의 목표로 삼을 것인지 (SLO : Service Level Object)를 정하는 프렉틱스 관점이 더 중요하다.  이를 구글에서는 SRE (Site Reliability Engineering)이라고 하는데, 이에 대한 자세한 내용은 https://landing.google.com/sre/book.html 를 참고하기 바란다.

이런 프렉틱스를 구축하는데 목적을 두고, 모니터링을 위한 툴링등은 직접 구축하는 것보다는 클라우드에서 제공하는 스택 드라이버와 같은 솔루션이나 데이타독(Datadog)와 같은 전문화된 모니터링 툴로 구축을 해서 시간을 줄이고, 프렉틱스 자체에 시간과 인력을 더 투자하는 것을 권장한다.




쿠버네티스 #13

모니터링 1/2


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


시스템을 운영하는데 있어서 운영 관점에 있어서 가장 중요한 기능중의 하나는 시스템에 대한 모니터링이다. 시스템 자원의 사용량이나 에러등에 대한 모니터링을 통해서, 시스템을 안정적으로 운영하고 문제 발생시 원인 파악과 대응을 할 수 있다.

이번 글에서는 쿠버네티스 모니터링 시스템에 대한 개념과, 아키텍쳐 그리고 구축 방법에 대해서 소개하고자 한다.

쿠버네티스 모니터링 컨셉

쿠버네티스에 대한 모니터링을 보면 많은 툴과 지표들이 있어서 혼돈하기 쉬운데, 먼저 모니터링 컨셉에 대한 이해를 할 필요가 있다.

쿠버네티스 기반의 시스템을 모니터링하기 위해서는 크게 아래와 같이 4가지 계층을 모니터링해야 한다.



1. 호스트 (노드)

먼저 쿠버네티스 컨테이너를 실행하는 하드웨어 호스트 즉 노드에 대한 지표 모니터링이 필요하다. 노드의 CPU,메모리, 디스크, 네트워크 사용량과, 노드 OS와 커널에 대한 모니터링이 이에 해당한다.

2. 컨테이너

다음은 노드에서 기동되는 각각의 컨테이너에 대한 정보이다. 컨테이너의 CPU,메모리, 디스크, 네트워크 사용량등을 모니터링 한다.

3. 애플리케이션

컨테이너안에서 구동되는 개별 애플리케이션의 지표를 모니터링 한다. 예를 들어, 컨테이너에서 기동되는 node.js 기반의 애플리케이션의 응답시간, HTTP 에러 빈도등을 모니터링한다.

4. 쿠버네티스

마지막으로, 컨테이너를 컨트롤 하는 쿠버네티스 자체에 대한 모니터링을한다. 쿠버네티스의 자원인 서비스나 POD, 계정 정보등이 이에 해당한다.

쿠버네티스 기반의 시스템 모니터링에 대해서 혼돈이 오는 부분중의 하나가 모니터링이라는 개념이 포괄적이기 때문이다. 우리가 여기서 다루는 모니터링은 자원에 대한 지표 대한 모니터링이다. 포괄적인 의미의 모니터링은 로그와, 에러 모니터링등 다양한 내용을 포괄한다.  

쿠버네티스 로깅

지표 모니터링과 함께 중요한 모니터링 기능중 하나는 로그 수집 및 로그 모니터링이다.

로그 수집 및 로그 모니터링 방법은 여러가지 방법이 있지만, 오픈소스 로그 수집 및 모니터링 조합인 EFK (Elastic search + FluentD + Kibina) 스택을 이용하는 경우가 대표적이다.

Fluentd 에이전트를 이용하여, 각종 로그를 수집하여, Elastic search에 저장하고, 저장된 지표를 Kibana 대쉬 보들르 이용하여 시작화 해서 나타내는 방법이 있다.

이에 대한 자세한 설명을 생략한다.

쿠버네티스 모니터링 시스템 구축

그러면 이러한 모니터링 시스템을 어떻게 구축할 것인가?

쿠버네티스 모니터링은 버전업 과정에서 많은 변화를 겪고 있다. 기존 모니터링 시스템의 아키텍쳐는 cAdvisor,Heapster를 이용하는 구조였으나, 이 아키텍쳐는 곧 deprecated 될 예정이고, Prometheus등 다양한 모니터링 아키텍쳐가 후보로 고려 되고 있다.

아래 그래프를 보면 재미있는 통계 결과가 있는데, cAdvisor,Heapster,Promethus 를 이용하는 방법도 있지만, 클라우드의 경우에는 클라우드 벤더에서 제공하는 쿠버네티스 모니터링 솔루션을 그대로 사용하거나 (18%) 또는 데이타독이나 뉴렐릭 (Datadog, newRelic)과 같이 전문화된 모니터링 클라우드을 사용하는 비율 (26%) 도 꽤 높다.



<그림. 쿠버네티스 모니터링 솔루션 분포 >

출처 :  https://thenewstack.io/5-tools-monitoring-kubernetes-scale-production/


개인적인 의견으로는 직접 모니터링 솔루션을 구축해서 사용하는 것보다는 비용은 약간 들지만 클라우드 벤더에서 제공되는 모니터링 도구나 또는 데이타독과 같은 전문 모니터링 솔루션을 이용하는 것을 추천한다.


직접 모니터링 솔루션을 구축할 경우 구축과 운영에 드는 노력도 꽤 크고, 또한 어떠한 지표를 모니터링해야할지 등에 대한 추가적인 노하우가 필요하다. 또한 cAdvisor,Heapster,Promethues 조합은 호스트와 컨테이너 그리고 쿠버네티스에 대한 모니터링은 제공하지만 애플리케이션 지표에 대한 모니터링과 로깅 기능은 제공하지 않기 때문에 별도의 구축이 필요하다. 이런 노력을 들이는 것 보다는 모든 기능이 한번에 제공되고 운영을 대행해주는 데이타독이나 클라우드에서 제공해주는 모니터링 솔루션을 사용하는 것을 추천한다.

Heapster 기반 모니터링 아키텍처

이러한 모니터링 요건을 지원하기 위해서, 쿠버네티스는 자체적인 모니터링 컴포넌트를 가지고 있는데, 그 구조는 다음과 같다.



<그림. 쿠버네티스 모니터링 시스템 아키텍쳐>

출처 Source : https://www.datadoghq.com/blog/how-to-collect-and-graph-kubernetes-metrics/


cAdvisor

cAdvisor는 모니터링 에이전트로, 각 노드마다 설치되서 노드에 대한 정보와 컨테이너 (Pod)에 대한 지표를 수집하여, Kubelet으로 전달한다.

Heapster

cAdvisor에 의해 수집된 지표는 Heapster 라는 중앙 집중화된 지표 수집 시스템에 모이게 되고, Heapster는 수집된 지표를 스토리지 백앤드에 저장한다.

Storage backend

Heapster가 지표를 저장하는 데이타베이스를 스토리지 백앤드라고 하는데, Heapster는 확장성을 위해서 다양한 스토리지 백앤드를 플러그인 구조를 선택하여 연결할 수 있다.

현재 제공되는 대표적인 스토리지 백앤드는 구글 클라우드의 모니터링 시스템인 스택드라이버 (stackdriver), 오픈 소스 시계열 데이타베이스인 인플럭스 디비 (InfluxDB) 등을 지원한다.

그래프 대쉬 보드

이렇게 저장된 모니터링 지표는 그래프와 같은 형태로 시각화 될필요가 있는데, 스토리지 백앤드를 지원하는 다양한 시각화 도구를 사용할 수 있다. 구글의 모니터링 시스템인 스택드라이버의 경우에는 자체적인 대쉬보드 및 그래프 인터페이스가 있고, 인플럭스 디비나 프로메테우스의 경우에는 오픈소스 시각화 도구인 그라파나(Grafana)를 사용할 수 있다.


<그림. 그라파나와 프로메테우스를 연결하여, 지표 모니터링을 시각화 한 예제>


그러나 이 아키텍쳐는 deprecation 계획이 시작되서 1.13 버전 부터는 완전히 제거될 예정이다.

https://github.com/kubernetes/heapster/blob/master/docs/deprecation.md


쿠버네티스 대시보드

다른 방법으로는 쿠버네티스를 모니터링 하고 관리할 수 있는 쉬운 방법이 하나 있는데, 쿠버네티스 대시보드를 사용하는 방법이다. 쿠버네티스는 기본적으로 kubectl이라는 커맨드 라인 인터페이스 (이하 CLI : Command Line Interface)를 사용하지만, 추가적으로 웹 기반의 관리 콘솔을 제공한다. 이를 쿠버네티스 대시보드라고 한다. (https://github.com/kubernetes/dashboard)

대시 보드 설치

쿠버네티스 대시 보드 설치 방법은 간단하다. 아래와 같이 대시보드 설정 yaml 파일을 이용하면 간단하게 대시 보드를 쿠버네티스 클러스터에 설치할 수 있다.


% kubectl create -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml


일반적인 경우에는 위의 스크립트로 설치가 가능하지만, 구글 클라우드 쿠버네티스 엔진의의 경우에는 설치 중에 권한 관련 에러가 나올 수 있는데, 구글 클라우드 쿠버네티스 엔진의 경우에는 보안을 이유로 일반적인 쿠버네티스보다 권한 설정 레벨이 높게 설정되어 있기 때문이다. 구글 클라우드 쿠버네티스 엔진에서 대시보드를 설치하고자할때에는 위의 스크립트를 실행하기 전에 먼저 아래 명령어를 이용해서, 현재 사용자 계정에 대해서 cluster-admin 롤을 부여해줘야 한다.  


%kubectl create clusterrolebinding cluster-admin-binding \
--clusterrole cluster-admin --user $(gcloud config get-value account)

대시 보드 접속

대시보드 설치가 끝났으면, 대시보드를 접속해보자

대시보드는 외부 서비스로 제공되지 않고, 내부 IP로만 접속이 가능한데, 클러스터 외부에서 접근하려면 kubectl proxy를 이용하면, 간단하게 접근이 가능하다.

kubectl proxy는 로컬 머신 (예를 들어 노트북)과 쿠버네티스 클러스터간의 통신을 프록싱해줘서, 로컬 머신에서 쿠버네티스 클러스터내의 HTTP 서비스를 접근할 수 있도록 해준다.

사용 방법은 로컬 머신에서 간단하게

%kubectl proxy

명령을 실행해주면 localhost:8001 포트를 통해서 쿠버네티스 클러스터로 트래픽을 프록시 해준다.

위와 같이 proxy를 실행한후에,  아래 URL로 접근을 하면, 대시보드 콘솔에 접근할 수 있다.

http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/


URL에 접근하면 아래와 같이 로그인 창이 나타난다.



사용자 계정 및 토큰등에 대해서는 보안 부분에서 별도로 다루기로 하겠다.

대쉬보드를 사용하기 위해서는 사용자 인증이 필요한데, 간단하게 인증을 위한 토큰을 사용하는 방법을 이용하도록 하겠다.

토큰은 쿠버네티스 API 인증 메커니즘중의 하나로, 여기서는 admin-user라는 계정을 하나 만든후에, 그 계정에, 클러스터 관리자롤을 부여한 후에, 그 사용자의 토큰을 사용하는 방법을 사용하겠다.


먼저 아래 스크립트를 이용해서 admin-user 라는 사용자를 생성한다.

admin-user.yaml 파일

apiVersion: v1

kind: ServiceAccount

metadata:

 name: admin-user

 namespace: kube-system


다음 아래 스크립트를 이용해서 cluster-admin 롤을 앞에서 생성한 admin-user에 부여한다.

admin-rolebinding.yaml 파일

apiVersion: rbac.authorization.k8s.io/v1beta1

kind: ClusterRoleBinding

metadata:

 name: admin-user

roleRef:

 apiGroup: rbac.authorization.k8s.io

 kind: ClusterRole

 name: cluster-admin

subjects:

- kind: ServiceAccount

 name: admin-user

 namespace: kube-system


다음 아래 명령어를 이용하면 admin-user의 토큰 값을 알 수 있다.

% kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')


명령을 실행하면 아래와 같이 토큰이 출력된다.


이 토큰 값을 앞의 로그인 창에 입력하면, 대시보드에 로그인할 수 있다.

대시 보드에 로그인하면 아래와 같이 노드나, Pod, 서비스등 쿠버네티스의 자원의 대부분의 정보에 대한 모니터링이 가능하다.




또한 kubectl CLI 명령을 사용하지 않고도 손쉽게 Deployment 등 각종 자원을 생성할 수 있다.


로그 부분에 들어가면 아래와 같이 로그 정보를 볼 수 있다



재미있는 기능중 하나는 아래 그림과 같이 특정 Pod의 컨테이너를 선택하면, 웹콘솔상에서 해당 컨테이너로 SSH 로그인이 가능하다.



여기서 다룬 쿠버네티스 대시보드 설정 및 로그인 부분은 프록시 사용, 로그인을 토큰을 사용하는 등, 운영환경에는 적절하지 않은 방법이다. 개발환경이나 테스트 용도로만 사용하도록 하고, 운영 환경에서는 사용자 계정 시스템 생성과 적절한 권한 배정을 한 후에, 적절한 보안 인증 시스템을 마련한 후에 적용하도록 하자.




쿠버네티스 #11

ConfigMap


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



애플리케이션을 배포하다 보면, 환경에 따라서 다른 설정값을 사용하는 경우가 있다. 예를 들어, 데이타베이스의 IP, API를 호출하기 위한 API KEY, 개발/운영에 따른 디버그 모드, 환경 설정 파일들이 있는데, 애플리케이션 이미지는 같지만, 이런 환경 변수가 차이가 나는 경우 매번 다른 컨테이너 이미지를 만드는 것은 관리상 불편할 수 밖에 없다.

이러한 환경 변수나 설정값들을 변수로 관리해서 Pod가 생성될때 이 값을 넣어줄 수 있는데, 이러한 기능을 제공하는 것이 바로 Configmap과 Secret이다.


아래 그림과 같이 설정 파일을 만들어놓고, Pod 를 배포할때 마다 다른 설정 정보를 반영하도록 할 수 있다.



Configmap이나 secret에 정의해놓고, 이 정의해놓은 값을 Pod로 넘기는 방법은 크게 두가지가 있다.

  • 정의해놓은 값을 Pod의 환경 변수 (Environment variable)로 넘기는 방법

  • 정의해놓은 값을 Pod의 디스크 볼륨으로 마운트 하는 방법

ConfigMap

configmap은 앞서 설명한것과 같이 설정 정보를 저장해놓는 일종의 저장소 역할을 한다.

configmap은 키/밸류 형식으로 저장이된다.

configmap을 생성하는 방법은 literal (문자)로 생성하는 방법과 파일로 생성하는 방법 두가지가 있다.

Literal

먼저 간단하게 문자로 생성하는 방법을 알아보자

키가 “language”로 하고 그 값이 “java”인 configMap을 생성해보자

Kubectl create configmap [configmap 이름] --from-literal=[키]=[값] 식으로 생성하면 된다.

아래 명령을 이용하면, hello-cm 이라는 이름의 configMap에 키는 language, 값은 java인 configMap이 생성된다.

% kubectl create configmap hello-cm --from-literal=language=java


또는 아래와 같이 YAML파일로도 configMap을 생성할 수 있다.

hello-cm.yaml

apiVersion: v1

kind: ConfigMap

metadata:

 name: hello-cm

data:

 language: java


데이타 항목에 [키]:[값] 형식으로 라인을 추가하면 여러개의 값을 하나의 configMap에 저장할 수 있다.

configmap이 생성되었으면 이 값을 Pod에서 환경 변수로 불러서 사용해보도록 하자.

node.js로 간단한 웹 애플리케이션을 만든후에 “LANGUAGE”라는 환경 변수의 값을 읽어서 출력하도록 할것이다.

아래와 같이 server.js node.js 애플리케이션을 만든다.


var os = require('os');


var http = require('http');

var handleRequest = function(request, response) {

 response.writeHead(200);

 response.end(" my prefered language is "+process.env.LANGUAGE+ "\n");


 //log

 console.log("["+

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

"] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);


이 파일을 컨테이너로 패키징한 후에, 아래와 같이 Deployment를 정의한다

apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: cm-deployment

spec:

 replicas: 3

 minReadySeconds: 5

 selector:

   matchLabels:

     app: cm-literal

 template:

   metadata:

     name: cm-literal-pod

     labels:

       app: cm-literal

   spec:

     containers:

     - name: cm

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

       imagePullPolicy: Always

       ports:

       - containerPort: 8080

       env:

       - name: LANGUAGE

         valueFrom:

           configMapKeyRef:

              name: hello-cm

              key: language


configMap에서 데이타를 읽는 부분은 맨 아래에 env 부분인데, env 부분에 환경 변수를 정의하는데, name은 LANGUAGE라는 이름으로 정의하고 데이타는 valueFrom을 이용해서 configMap에서 읽어오도록 하였다. name에는 configMap의 이름인 hello-cm을, 그리고 읽어오고자 하는 데이타는 키 값이 “language”인 값을 읽어오도록 하였다. 이렇게 하면, LANGUAGE 환경 변수에, configMap에 “language” 로 저장된 “java”라는 문자열을 읽어오게 된다.


이 스크립트를 이용하여 Deployment를 생성한 후에, 이 Deployment 앞에 Service (Load balancer)를 붙여 보자.


apiVersion: v1

kind: Service

metadata:

 name: cm-literal-svc

spec:

 selector:

   app: cm-literal

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

 type: LoadBalancer


서비스가 생성이 되었으면 웹 브라우져에서 해당 Service의 URL을 접속해보자.



위와 같이 환경 변수에서 “java”라는 문자열을 읽어와서 출력한것을 확인할 수 있다.

File

위와 같이 개개별 값을 공유할 수 도 있지만, 설정을 파일 형태로 해서 Pod에 공유하는 방법도 있다.

예제를 보면서 이해하도록 하자.

profile.properties라는 파일이 있고 파일 내용이 아래와 같다고 하자

myname=terry

email=myemail@mycompany.com

address=seoul


파일을 이용해서 ConfigMap을 만들때는 아래와 같이 --from-file 을 이용해서 파일명을 넘겨주면 된다.

kubectl create configmap cm-file --from-file=./properties/profile.properties

이렇게 파일을 이용해서 configMap을 생성하면, 아래와 같이 키는 파일명이 되고, 값은 파일 내용이 된다.


환경변수로 값을 전달하기

생성된 configMap 내의 값을 Pod로 전달하는 방법은,앞에서 예를 든것과 같이 환경 변수로 넘길 수 있다.

아래 Deployment 예제를 보면


apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: cm-file-deployment

spec:

 replicas: 3

 minReadySeconds: 5

 selector:

   matchLabels:

     app: cm-file

 template:

   metadata:

     name: cm-file-pod

     labels:

       app: cm-file

   spec:

     containers:

     - name: cm-file

       image: gcr.io/terrycho-sandbox/cm-file:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080

       env:

       - name: PROFILE

         valueFrom:

           configMapKeyRef:

              name: cm-file

              key: profile.properties


cm-file configMap에서 키가 “profile.properties” (파일명)인 값을 읽어와서 환경 변수 PROFILE에 저장한다. 저장된 값은 파일의 내용인 아래 문자열이 된다.

myname=terry

email=myemail@mycompany.com

address=seoul


혼동하지 말아야 하는 점은, profile.properties 파일안에 문자열이 myname=terry 처럼 키/밸류 형식으로 되어 있다고 하더라도, myname 을 키로 해서 terry라는 값을 가지고 오는 것처럼 개개별 문자열을 키/밸류로 인식하는 것이 아니라 전체 파일 내용을 하나의 문자열로 처리한다는 점이다.


디스크 볼륨으로 마운트하기

configMap의 정보를 pod로 전달하는 방법은 앞에 처럼 환경 변수를 사용하는 방법도 있지만, Pod의 디스크 볼륨으로 마운트 시키는 방법도 있다.

앞의 cm-file configMap을 /tmp/config/에 마운트 해보도록 하자.

아래와 같이 Deployment 스크립트를 작성한다.


apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: cm-file-deployment-vol

spec:

 replicas: 3

 minReadySeconds: 5

 selector:

   matchLabels:

     app: cm-file-vol

 template:

   metadata:

     name: cm-file-vol-pod

     labels:

       app: cm-file-vol

   spec:

     containers:

     - name: cm-file-vol

       image: gcr.io/terrycho-sandbox/cm-file-volume:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080

       volumeMounts:

         - name: config-profile

           mountPath: /tmp/config

     volumes:

       - name: config-profile

         configMap:

           name: cm-file


configMap을 디스크 볼륨으로 마운트해서 사용하는 방법은 volumes 을 configMap으로 정의하면 된다. 위의 예제에서 처럼 volume을 정의할때, configMap으로 정의하고 configMap의 이름을 cm-file로 정의하여, cm-file configMap을 선택하였다. 이 볼륨을 volumeMounts를 이용해서 /tmp/config에 마운트 되도록 하였다.

이때 중요한점은 마운트 포인트에 마운트 될때, 파일명을 configMap내의 키가 파일명이 된다.


다음 테스트를 위해서 server.js 애플리케이션에 /tmp/config/profile.properties 파일을 읽어서 출력하도록 아래와 같이 코드를 작성한다.

var os = require('os');

var fs = require('fs');


var http = require('http');

var handleRequest = function(request, response) {

 fs.readFile('/tmp/config/profile.properties',function(err,data){

   response.writeHead(200);

   response.end("Read configMap from file  "+data+" \n");

 });


 //log

 console.log("["+

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

"] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);


이 server.js를 도커로 패키징해서 배포한후, service를 붙여서 테스트해보면 다음과 같은 결과를 얻을 수 있다.



파일 내용이 출력되는 것을 확인할 수 있다

디스크에 마운트가 제대로 되었는지를 확인하기 위해서 Pod에 쉘로 로그인해서 확인해보자


그림과 같이 /tmp/config/profile.properties 파일이 생성된것을 확인할 수 있다.



쿠버네티스 #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라는 메세지가 뜬다. 인증서 정보를 확인해보면 아래와 같이 앞서 생성한 인증서에 대한 정보가 들어가 있는 것을 확인할 수 있다.



쿠버네티스 #7

서비스 (service)


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


Service

쿠버네티스 서비스에 대해서 자세하게 살펴보도록 한다.

Pod의 경우에 지정되는 Ip가 랜덤하게 지정이 되고 리스타트 때마다 변하기 때문에 고정된 엔드포인트로 호출이 어렵다, 또한 여러 Pod에 같은 애플리케이션을 운용할 경우 이 Pod 간의 로드밸런싱을 지원해줘야 하는데, 서비스가 이러한 역할을 한다.

서비스는 지정된 IP로 생성이 가능하고, 여러 Pod를 묶어서 로드 밸런싱이 가능하며, 고유한 DNS 이름을 가질 수 있다.


서비스는 다음과 같이 구성이 가능하며, 라벨 셀렉터 (label selector)를 이용하여, 관리하고자 하는 Pod 들을 정의할 수 있다.


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - port: 80

     protocol: TCP

     targetPort: 8080

 type: LoadBalancer

멀티 포트 지원

서비스는 동시에 하나의 포트 뿐 아니라 여러개의 포트를 동시에 지원할 수 있다. 예를 들어 웹서버의 HTTP와 HTTPS 포트가 대표적인 예인데,  아래와 같이 ports 부분에 두개의 포트 정보를 정의해주면 된다.

apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

   - name: https

     port: 443

     protocol: TCP

     targetPort: 8082

 type: LoadBalancer

로드 밸런싱 알고리즘

서비스가 Pod들에 부하를 분산할때 디폴트 알고리즘은 Pod 간에 랜덤으로 부하를 분산하도록 한다.

만약에 특정 클라이언트가 특정 Pod로 지속적으로 연결이 되게 하려면  Session Affinity를 사용하면 되는데, 서비스의 spec 부분에 sessionAffinity: ClientIP로 주면 된다.




웹에서 HTTP Session을 사용하는 경우와 같이 각 서버에 각 클라이언트의 상태정보가 저장되어 있는 경우에 유용하게 사용할 수 있다.

Service Type

서비스는 IP 주소 할당 방식과 연동 서비스등에 따라 크게 4가지로 구별할 수 있다.

  • Cluster IP

  • Load Balancer

  • Node IP

  • External name


ClusterIP

디폴트 설정으로, 서비스에 클러스터 IP (내부 IP)를 할당한다. 쿠버네티스 클러스터 내에서는 이 서비스에 접근이 가능하지만, 클러스터 외부에서는 외부 IP 를 할당  받지 못했기 때문에, 접근이 불가능하다.

Load Balancer

보통 클라우드 벤더에서 제공하는 설정 방식으로, 외부 IP 를 가지고 있는 로드밸런서를 할당한다. 외부 IP를 가지고 있기  때문에, 클러스터 외부에서 접근이 가능하다.

NodePort

클러스터 IP로만 접근이 가능한것이 아니라, 모든 노드의 IP와 포트를 통해서도 접근이 가능하게 된다. 예를 들어 아래와 같이 hello-node-svc 라는 서비스를 NodePort 타입으로 선언을 하고, nodePort를 30036으로 설정하면, 아래 설정에 따라 클러스터 IP의  80포트로도 접근이 가능하지만, 모든 노드의 30036 포트로도 서비스를 접근할 수 있다.


hello-node-svc-nodeport.yaml


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 type: NodePort

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

     nodePort: 30036


아래 그림과 같은 구조가 된다.




이를 간단하게 테스트 해보자.

아래는 구글 클라우드에서 쿠버네티스 테스트 환경에서 노드로 사용되고 있는 3개의 VM 목록과 IP 주소이다.


현재 노드는 아래와 같이 3개의 노드가 배포되어 있고 IP 는 10.146.0.8~10이다.

내부 IP이기 때문에, VPC 내의 내부 IP를 가지고 있는 서버에서 테스트를 해야 한다.


같은 내부 IP를 가지고 있는 envoy-ubuntu 라는 머신 (10.146.0.18)에서 각 노드의 30036 포트로 curl을 테스트해본 결과 아래와 같이 모든 노드의 IP를 통해서 서비스 접근이 가능한것을 확인할 수 있다.



ExternalName

ExternalName은 외부 서비스를 쿠버네티스 내부에서 호출하고자할때 사용할 수 있다.

쿠버네티스 클러스터내의 Pod들은 클러스터 IP를 가지고 있기 때문에 클러스터 IP 대역 밖의 서비스를 호출하고자 하면, NAT 설정등 복잡한 설정이 필요하다.

특히 AWS 나 GCP와 같은 클라우드 환경을 사용할 경우 데이타 베이스나, 또는 클라우드에서 제공되는 매지니드 서비스 (RDS, CloudSQL)등을 사용하고자할 경우에는 쿠버네티스 클러스터 밖이기 때문에, 호출이 어려운 경우가 있는데, 이를 쉽게 해결할 수 있는 방법이 ExternalName 타입이다.

아래와 같이 서비스를 ExternalName 타입으로 설정하고, 주소를 DNS로  my.database.example.com으로 설정해주면 이 my-service는 들어오는 모든 요청을 my.database.example.com 으로 포워딩 해준다. (일종의 프록시와 같은 역할)

kind: Service
apiVersion: v1
metadata:
 name: my-service
 namespace: prod
spec:
 type: ExternalName
 externalName: my.database.example.com

다음과 같은 구조로 서비스가 배포된다.



DNS가 아닌 직접 IP를 이용하는 방식

위의 경우 DNS를 이용하였는데, DNS가 아니라 직접 IP 주소를 이용하는 방법도 있다.

서비스 ClusterIP 서비스로 생성을 한 후에, 이 때 서비스에 속해있는 Pod를 지정하지 않는다.

apiVersion: v1

kind: Service

metadata:

 name: external-svc-nginx

spec:

 ports:

 - port: 80



다음으로, 아래와 같이 서비스의 EndPoint를 별도로 지정해주면 된다.

apiVersion: v1

kind: Endpoints

metadata:

 name: external-svc-nginx

subsets:

 - addresses:

   - ip: 35.225.75.124

   ports:

   - port: 80


이 때 서비스명과 서비스 EndPoints의 이름이 동일해야 한다. 위의 경우에는 external-svc-nginx로 같은 서비스명을 사용하였고 이 서비스는 35.225.75.124:80 서비스를 가르키도록 되어 있다.

그림으로 구조를 표현해보면 다음과 같다.




35.225.75.124:80 은 nginx 웹서버가 떠 있는 외부 서비스이고, 아래와 같이 간단한 문자열을 리턴하도록 되어 있다.



이를 쿠버네티스 내부 클러스터의 Pod 에서 curl 명령을 이용해서 호출해보면 다음과 같이 외부 서비스를 호출할 수 있음을 확인할 수 있다.

Headless Service

서비스는 접근을 위해서 Cluster IP 또는 External IP 를 지정받는다.

즉 서비스를 통해서 제공되는 기능들에 대한 엔드포인트를 쿠버네티스 서비스를 통해서 통제하는 개념인데, 마이크로 서비스 아키텍쳐에서는 기능 컴포넌트에 대한 엔드포인트 (IP 주소)를 찾는 기능을 서비스 디스커버리 (Service Discovery) 라고 하고, 서비스의 위치를 등록해놓는 서비스 디스커버리 솔루션을 제공한다. Etcd 나 hashcorp의 consul (https://www.consul.io/)과 같은 솔루션이 대표적인 사례인데, 이 경우 쿠버네티스 서비스를 통해서 마이크로 서비스 컴포넌트를 관리하는 것이 아니라, 서비스 디스커버리 솔루션을 이용하기 때문에, 서비스에 대한 IP 주소가 필요없다.

이런 시나리오를 지원하기 위한 쿠버네티스의 서비스를 헤드리스 서비스 (Headless service) 라고 하는데, 이러한 헤드리스 서비스는 Cluster IP등의 주소를 가지지 않는다. 단 DNS이름을 가지게 되는데, 이 DNS 이름을 lookup 해보면, 서비스 (로드밸런서)의 IP 를 리턴하지 않고, 이 서비스에 연결된 Pod 들의 IP 주소들을 리턴하게 된다.


간단한 테스트를 해보면


와 같이 기동중인 Pod들이 있을때, Pod의 IP를 조회해보면 다음과 같다.


10.20.0.25,10.20.0.22,10.20.0.29,10.20.0.26 4개가 되는데,

다음 스크립트를 이용해서 hello-node-svc-headless 라는 헤드리스 서비스를 만들어보자


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc-headless

spec:

 clusterIP: None

 selector:

   app: hello-node

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080


아래와 같이 ClusterIP가 할당되지 않음을 확인할 수 있다.



다음 쿠버네티스 클러스터내의 다른 Pod에서 nslookup으로 해당 서비스의 dns 이름을 조회해보면 다음과 같이 서비스에 의해 제공되는 pod 들의 IP 주소 목록이 나오는 것을 확인할 수 있다.




Service discovery

그러면 생성된 서비스의 IP를 어떻게 알 수 있을까? 서비스가 생성된 후 kubectl get svc를 이용하면 생성된 서비스와 IP를 받아올 수 있지만, 이는 서비스가 생성된 후이고, 계속해서 변경되는 임시 IP이다.

DNS를 이용하는 방법

가장 쉬운 방법으로는 DNS 이름을 사용하는 방법이 있다.

서비스는 생성되면 [서비스 명].[네임스페이스명].svc.cluster.local 이라는 DNS 명으로 쿠버네티스 내부 DNS에 등록이 된다. 쿠버네티스 클러스터 내부에서는 이 DNS 명으로 서비스에 접근이 가능한데, 이때 DNS에서 리턴해주는 IP는 외부 IP (External IP)가 아니라 Cluster IP (내부 IP)이다.


아래 간단한 테스트를 살펴보자. hello-node-svc 가 생성이 되었는데, 클러스터내의 pod 중 하나에서 ping으로 hello-node-svc.default.svc.cluster.local 을 테스트 하니, hello-node-svc의 클러스터 IP인 10.23.241.62가 리턴되는 것을 확인할 수 있다.



External IP (외부 IP)

다른 방식으로는 외부 IP를 명시적으로 지정하는 방식이 있다. 쿠버네티스 클러스터에서는 이 외부 IP를 별도로 관리하지 않기 때문에, 이 IP는 외부에서 명시적으로 관리되어야 한다.


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

 externalIPs:

 - 80.11.12.11

 

외부 IP는 Service의 spec 부분에서 externalIPs 부분에 IP 주소를 지정해주면 된다.

구글 클라우드의 경우

퍼블릭 클라우드 (AWS, GCP 등)의 경우에는 이 방식 보다는 클라우드내의 로드밸런서를 붙이는 방법을 사용한다.


구글 클라우드의 경우를 살펴보자.서비스에 정적인 IP를 지정하기 위해서는 정적 IP를 생성해야 한다. 구글 클라우드 콘솔내의 VPC 메뉴의 External IP 메뉴에서 생성해도 되고, 아래와 같이 gcloud CLI 명령어를 이용해서 생성해도 된다.


IP를 생성하는 명령어는 gcloud compute addresses create [IP 리소스명] --region [리전]

을 사용하면 된다. 구글 클라우드의 경우에는 특정 리전만 사용할 수 있는 리저널 IP와, 글로벌에 모두 사용할 있는 IP가 있는데, 서비스에서는 리저널 IP만 사용이 가능하다. (글로벌 IP는 후에 설명하는 Ingress에서 사용이 가능하다.)

아래와 같이

%gcloud compute addresses create hello-node-ip-region  --region asia-northeast1

명령어를 이용해서 asia-northeast1 리전 (일본)에 hello-node-ip-region 이라는 이름으로 Ip를 생성하였다. 생성된 IP는 describe 명령을 이용해서 확인할 수 있으며, 아래 35.200.64.17 이 배정된것을 확인할 수 있다.



이 IP는 서비스가 삭제되더라도 계속 유지되고, 다시 재 사용이 가능하다.

그러면 생성된 IP를 service에 적용해보자

다음과 같이 hello-node-svc-lb-externalip.yaml  파일을 생성하자


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

 type: LoadBalancer

 loadBalancerIP: 35.200.64.17


타입을 LoadBalancer로 하고, loadBalancerIP 부분에 앞에서 생성한 35.200.64.17 IP를 할당한다.

다음 이 파일을 kubectl create -f hello-node-svc-lb-externalip.yaml 명령을 이용해서 생성하면, hello-node-svc 가 생성이 되고, 아래와 같이 External IP가 우리가 앞에서 지정한 35.200.64.17 이 지정된것을 확인할 수 있다.




쿠버네티스 #6

Replication Controller를 이용하여 서비스 배포하기

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


1. 도커 파일 만들기

node.js로 간단한 웹서버를 만들어서 도커로 패키징 해보자.

실습을 진행하기 위해서 로컬 환경에 도커와, node.js 가 설치되어 있어야 한다. 이 두 부분은 생략하도록 한다.

여기서 사용한 실습 환경은 node.js carbon 버전 (8.11.3), 도커 맥용 18.05.0-ce, build f150324 을 사용하였다.

node.js 애플리케이션 준비하기

node.js로 간단한 웹 애플리케이션을 제작해보자 server.js라는 이름으로 아래 코드를 작성한다.

var os = require('os');