며칠 전 Kubernetes Cluster 를 v1.16.3 에서 v1.18.5 로 업그레이드 하다가 발견된 이슈를 해결한 내용을 공유합니다.

 

제가 관리하고 있는 Kubernetes 는 Kubespray 로 설치했었고 업그레이드 역시 Kubespray 를 활용했습니다.

Kubespray 는 Kubernetes 설치에 kubeadm 을 사용하는데 kubeadm 은 마이너 버전 차이가 1 을 초과하면 업그레이드를 지원하지 않습니다.

그렇기 때문에 v1.16.x 에서 v1.18.x 로 업그레이드를 하려며 v1.16.x -> v1.17.x -> v1.18.x 로 단계적으로 업그레이드를 해야 합니다.

 

 

기존 설치된 버전은 kubespray 의 release-2.12 브랜치로 v1.16.3 이었고,  kubespray tags/v2.13.2 을 활용하면 v1.17.7 로 업그레이들 할 수 있습니다.

 

 

## deploy 디렉토리로 변경

$ cd ~/deploy/kubespray

 

 

## kubespray 다운로드 (tag 로 할 때)

$ git checkout master

$ git pull

$ git checkout -b tag_v2.13.2 tags/v2.13.2

$ sudo pip install -r requirements.txt

 

 

## inventory 복사 (업그레이드 할 때)

$ rm -rf inventory/k1-seungkyua/group_vars/*

$ cp -r inventory/sample/group_vars inventory/k1-seungkyua

 

 

 

## hosts.ini 수정

$ vi inventory/k1-seungkyua/inventory.ini

[all]

k1-master01 ansible_host=k1-master01 ip=192.168.30.13 iface=eno49

k1-master02 ansible_host=k1-master02 ip=192.168.30.14 iface=ens2f0

k1-master03 ansible_host=k1-master03 ip=192.168.30.15 iface=eno49

k1-node01 ansible_host=k1-node01 ip=192.168.30.12 iface=ens2f0

k1-node02 ansible_host=k1-node02 ip=192.168.30.17 iface=ens2f0

k1-node03 ansible_host=k1-node03 ip=192.168.30.18 iface=ens2f0

k1-node04 ansible_host=k1-node04 ip=192.168.30.21 iface=ens2f0

k1-node05 ansible_host=k1-node05 ip=192.168.30.20 iface=ens2f0

 

[etcd]

k1-master01

k1-master02

k1-master03

 

[kube-master]

k1-master01

k1-master02

k1-master03

 

[kube-node]

k1-node01

k1-node02

k1-node03

k1-node04

k1-node05

 

[calico-rr]

 

[k8s-cluster:children]

kube-master

kube-node

calico-rr

 

 

## k8s-cluster.yml 수정

$ vi inventory/k1-seungkyua/extra-vars.yml

 

enable_nodelocaldns: false

 

# 신규 설치시에만 세팅

# skydns_server: "{{ kube_service_addresses|ipaddr('net')|ipaddr(10)|ipaddr('address') }}"

 

kube_proxy_mode: ipvs

 

# hostname and hosts file

populate_inventory_to_hosts_file: false

override_system_hostname: false

 

# Helm deployment

helm_enabled: true

 

## copy admin.conf

kubeconfig_localhost: true

kubectl_localhost: true

 

## calico setting

ipip_mode: Never

peer_with_router: true

nat_outgoing: true

global_as_num: "65000"

peers:

  - router_id: "192.168.30.1"

    as: "65000"

 

# calico_ip_auto_method: "interface=eth.*"

# calico_ip_auto_method: "interface=ens6"

calico_ip_auto_method: "can-reach=8.8.8.8"

 

kubeadm_enabled: true

 

docker_insecure_registries:

  - tacorepo:5000

 

dashboard_enabled: true

 

# Local volume provisioner deployment

local_volume_provisioner_enabled: true

 

metrics_server_enabled: true

 

## rbd provisioner deployment

rbd_provisioner_enabled: true

rbd_provisioner_monitors: 192.168.30.23:6789,192.168.30.24:6789,192.168.30.25:6789

rbd_provisioner_pool: kubes

rbd_provisioner_secret: QVFBTmRVVlh5UmZoTlJBQTMyZTh6Qk5uajV1VElrMDJEbWFwWmc9PQ==

rbd_provisioner_user_secret: QVFDdC9CcFlpZ0o3TVJBQTV2eStjbDM5RXNLcFkzQyt0WEVHckE9PQ==

 

 

저의 경우에는 calico 를 활용하여 switch 와 bgp 로 통신하므로 bgp 설정을 넣었습니다.

 

 

 

## k8s 업그레이드 수행

$ ansible-playbook -e @inventory/k1-seungkyua/extra-vars.yml -b -f 30 -i inventory/k1-seungkyua/inventory.ini upgrade-cluster.yml

 

 

 

업그레이드를 돌리면 순차적으로 drain 과 uncordon 을 수행하는데 중간에 에러가 날 경우에는 kubelet 을 업그레이드 이전 버전으로 돌려놓아야 합니다.

 

예를 들어, master02 를 업그레이드 하다가 에러가 나면 아래의 작업을 수행해 줘야 합니다.

 

1. 에러가 나도 kubelet 의 버전은 올라가므로 kubelet 버전을 업그레이드 이전으로 다운그레이드 해야 합니다. 쉬운 방법은 다른 노드에 있는 kubelet 을 복사해서 kubelet 을 restart 해야 합니다.

systemctl restart kubelet

 

 

2. schedule 이 disabled 되어 있을 수 있으니 uncordon 해줘야 합니다.

kubectl uncordon k1-master02

  

 

 

 

 

## 업그레이드 시 에러 날 경우

PodDisruptionBudget 을 확인해야 함

istio 의 MIN AVAILABLE 은 전부 0 으로 세팅

elasticsearch 는 삭제

 

# kubectl get PodDisruptionBudget -A

NAMESPACE      NAME                          MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE

istio-system   istio-egressgateway           1               N/A               0                     76d

istio-system   istio-galley                  1               N/A               0                     76d

istio-system   istio-ingressgateway          1               N/A               0                     76d

istio-system   istio-pilot                   1               N/A               0                     76d

istio-system   istio-policy                  1               N/A               0                     76d

istio-system   istio-telemetry               1               N/A               0                     76d

logging        elasticsearch-k1-master-pdb   N/A             1                 0                     71d

 

Master의 root 디렉토리 확인

 

/root/.kube/http-cache 디렉토리 삭제

/root/.docker/config 파일 삭제 (도커 로그인정보를 가지고 있어서 docker pull 이 안될 수 있음)

 

coreDNS 가 10.233.0.3 이면 kubeadm 에서 coreDNS 는 10.233.0.10 이어야 한다는 업그레이드 에러 발생

 

kubeadm 명령어에 --print-config 옵션을 붙혀서 미리 한 번 실행하여 이상이 없는지 확인

 

kubeadm upgrade apply -y v1.17.7 --config=/etc/kubernetes/kubeadm-config.yaml --ignore-preflight-errors=all --allow-experimental-upgrades --allow-release-candidate-upgrades --etcd-upgrade=false --certificate-renewal=true --force --v=9 --print-config

 

실행에 이상이 없으면 kubespray 의 roles/kubernetes/master/tasks/kubeadm-upgrade.yml 에 해당 옵션을 추가

 

 

 

 

kubelet upgrade 시에 에러가 발생하였음.

kubelet 버전은 v1.16.3 에서 v1.17.7 로 업그레이드가 되었으나 기존 옵션으로 kubelet 이 start 시에 에러가 남

kubelet --version 으로 v1.17.7 이면 v1.16.3 으로 kubelet 을 다시 복사하고 Kubelet restart

 

 

 

 

 

 

Posted by Kubernetes Korea co-leader seungkyua@gmail.com

댓글을 달아 주세요

Istio 에 대한 문의도 있었고 또 모르시는 분들이 계셔서 글을 올립니다.

 

Istio 는 Kubernetes 에만 올릴 수 있고 Control plane 이 여러 pod 로 구성되어 있습니다.

1.4 버전까지는 아래 다이어그램에서 보이듯이 Control 에 해당하는 컴포넌트들이 각각의 프로세스로 실행되었습니다.  

 

하지만 1.5 버전 부터는 (현재는 1.6 버전이 최신입니다) 아래와 같이 New Architecture 로 변경되었습니다.

 

 

 

모든 컴포넌트가 istiod 라는 하나의 프로세스로 합쳐지고 Mixer 가 없어지고 Pilot 이 Mixer 의 기능까지도 함께 수행하는 것으로 되어 있습니다.

 

Istio 에서는 각 서비스를 관리하는 것도 중요하지만 사실 더 중요한 것은 여기 다이어그램에는 표시되지 않은 Ingress traffic 을 처리하는 Gateway 서버입니다. 모든 트래픽을 받아는 주는 관문 역할을 해야 하고, 백엔드의 Envoy Proxy 와도 연계되어야 하기 때문에 Isito 를 도입한다면 중요하게 고려되어야 할 서비스입니다. 

 

Istio 를 설치하는 방법은 아래 두가지 방법으로 하는 것이 편리합니다.

1. Download Istio

https://istio.io/latest/docs/setup/getting-started/

Istio 를 다운받아 istioctl 로 Configure Profile 을 활용하여 설치하는 방법입니다.

 

2. Standalone Operator Install

https://istio.io/latest/docs/setup/install/standalone-operator/

Istio Operator 를 설치하고 이를 기반으로 Istio 를 설치하는 방법입니다.

 

 

Posted by Kubernetes Korea co-leader seungkyua@gmail.com

댓글을 달아 주세요

Kubernetes 를 가르친다고 하면 어떻게 교육을 구성하면 효과적일지 생각해 봤다.

모든 과정이 그렇겠지만 가장 기본적인 내용을 먼저 설명하고 응용, ecosystem 까지 확대하는 순으로 계획해 보았는데, 최소한 5일이 필요한 것 같다.

사실 5일은 부족하지만 그래도 최소한 이정도는 알아야지... 하는 정도가 5일이다.

실습 환경으로 VM (4core, mem 8G, disk 300G)은 4대 (master 1대, node 2대, 스토리지 1대) 가 적절하다.

 

[ Kubernetes 교육 과정 ]

[ 1 day ]
▪ Container 설명 (4H)
  - Container 의 개념 설명
  - Docker, Docker Registry 설치, 실습
  - Dockerfile 작성 실습, 명령어 실습

▪ Kubernetes 설명 (2H)
  - Kubernetes 배경, 아키텍처 설명

▪ Kubernetes 설치 실습 (2H)
  - Kubespray 설명
  - Kubespray 를 활요한 Kubernetes 설치

[ 2 day ]
▪ Kubernetes 활용 (4H)
  - Kubernetes Cli 실습
  - Kubernetes Resource 활용 실습
    - Pod, ReplicaSet, Deployment, StatefulSet, DaemonSet, Job

▪ Kubernetes 네트워크 활용 (4H)
  - Calico 네트워크의 이해
  - Cilium 네트워크의 이해

[ 3 day ]
▪ Kubernetes 고급 활용 (2H)
  - 스토리지 PV (Persistent Volumen) 활용
  - Kubernetes Auto scaling 설치

▪ Helm 설명 (2H)
  - Helm 의 개념, 구조 설명
  - Helm 기본 패키지 생성 실습

▪ DevOps 구축 실습 (4H)
  - Kubernetes 기반 Jenkins 설치
  - Jenkins 를 활용한 Pipeline 구성 실습

[ 4 day ]
▪ Kubernetes Pattern 설명 (4H)
  - Kubernetes 에서 활용될 수 잇는 Pattern 을 설명
    - Init Container, Resource Request/Limit, Scheduling

▪ Kubernetes Programming 실습 (4H)
  - Golang 설명
  - Operator 설명
  - Custom Controller 개발 실습
  - Admission Webhook 설명

[ 5 day ]
▪ Kubernetes Monitoring 실습 (4H)
  - Prometheus / Grafana 설명, 설치, 실습
  - exporter 설명

▪ Kubernetes Logging 실습 (4H)
  - Elastic Search, FluentBit, Kibana 설명, 설치, 실습

 

 

------------------------- 보너스 ----------------------------------


[ 6 day - 한 분야당 2H 보다는 최소 하루가 적절할 듯 ]
▪ Kubernetes Ecosystem 설명 (8H)

  - Cluster-API 설명, 실습
  - Machine Learning Infra - Kubeflow 설명, 실습
  - MSA(Microservice Architecture) - Istio 설명, 실습
  - Continuous Delivery 설명 - ArgoCD 설명, 실습

 

 

Posted by Kubernetes Korea co-leader seungkyua@gmail.com

댓글을 달아 주세요

Kubernetes Upgrade 는 고려해야 할 사항이 많지만 Kubespray 를 활용하면 의외로 간단할 수 도 있다.

Kubespray 는 Kubernetes 설치에 kubeadm 을 활용하기 때문에 업그레이드는 Kubernetes 의 바로 윗단계 마이너버전까지만 가능하다. 예를 들어 지금 Kubernetes 버전이 v1.14.x 이면 v1.16.x 로는 업그레이드를 못한다. v1.16.x 로 업그레이드를 하기 위해서는 v1.15.x 로 업그레이드를 하고, v1.16.x 로 다시 업그레이드를 해야 한다.

 

마침 업그레이드가 필요했고 현재 설치된 Kubernetes 버전이 v1.14.3 이므로 v1.15.6 으로 업그레이드를 먼저 진행하였다.

$ cd kubespray

$ git branch
  master
* release-2.11
  release-2.12
  tag_v2.10.4
  tag_v2.9.0

Kubespray 의 release-2.11 branch 는 Kubernetes Kubernetes v1.15.6 이다. 아래 명령어로 확인할 수 버전을 확인할 수 있다.

kube_version: v1.15.6

$ grep -nr kube_version: .
./docs/upgrades.md:30:And then repeat with v1.4.6 as kube_version:
./inventory/sample/group_vars/k8s-cluster/k8s-cluster.yml:23:kube_version: v1.15.6
./inventory/k1-seungkyua/group_vars/k8s-cluster/k8s-cluster.yml:23:kube_version: v1.15.6
./roles/download/defaults/main.yml:52:kube_version: v1.15.6
./roles/kubespray-defaults/defaults/main.yaml:15:kube_version: v1.15.6
./RELEASE.md:37:  is bound to ``kube_version: 1.4.x``, ``calico_version: 0.22.0``, ``etcd_version: v3.0.6``,
./RELEASE.md:40:  And Kubespray v3.x.x shall be bound to ``kube_version: 2.x.x`` respectively.

먼저 Master 3대를 업그레이드 하여 업그레이드를 순차적으로 하는 것이 좋다. Kubernetes Master 와 Work Node 는 minor 차이가 나더

라도 큰 문제가 생기지 않기 때문에 순차적 검증을 할 수 있다. 물론 kube-apiserver 에서 활성화한 특정 feature 와 resource version 은 사전에 반드시 테스트하여 안전성을 확보한 후 진행해야 한다. 

$ ansible-playbook -e @inventory/k1-seungkyua/extra-vars.yml -b -f 30 -i inventory/k1-seungkyua/inventory.ini upgrade-cluster.yml --limit k1-master01,k1-master02,k1-master03

중요한 것은 --limit 옵션으로 Master 만 업그레이드를 진행한다는 것이다.

Every 2.0s: kubectl get nodes                                                                                                                                           Thu Jan  9 16:15:35 2020

NAME          STATUS   ROLES          AGE    VERSION
k1-master01   Ready    master         323d   v1.15.6
k1-master02   Ready    master         323d   v1.15.6
k1-master03   Ready    master         323d   v1.15.6
k1-node01     Ready    ingress,node   323d   v1.14.3
k1-node02     Ready    node           323d   v1.14.3
k1-node03     Ready    node           322d   v1.14.3
k1-node04     Ready    node           323d   v1.14.3
k1-node05     Ready    node           323d   v1.14.3

 

이제 Worker 를 업그레이드 할 차례인데, Worker 를 업그레이드는 고려해야 할 사항이 있다. 노드를 업그레이드 하기 위해서는 drain 을 하여 Pod 를 eviction 하는데 Kubespray 는 evction 이 완료될 때 까지 기다린다. DaemonSet 은 무시하는 옵션이 있지만 PodDisruptionBudget 은 policy 에 걸리면 pod 가 terminating 상태에서 완료되지 않기 때문에 timeout 으로 업그레이드에 실패하게 된다. 여기서는 elasticsearch 가 문제가 있어 timeout 나서 실패하였다.

# kubectl get PodDisruptionBudget -A
NAMESPACE      NAME                          MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
istio-system   istio-egressgateway           1               N/A               0                     76d
istio-system   istio-galley                  1               N/A               0                     76d
istio-system   istio-ingressgateway          1               N/A               0                     76d
istio-system   istio-pilot                   1               N/A               0                     76d
istio-system   istio-policy                  1               N/A               0                     76d
istio-system   istio-telemetry               1               N/A               0                     76d
logging        elasticsearch-k1-master-pdb   N/A             1                 0                     71d

elasticsearch 문제를 해결하고 다시 k1-node01 이라는 Worker 노드  1대만 업그레이드를 진행한다.

$ ansible-playbook -e @inventory/k1-seungkyua/extra-vars.yml -b -f 30 -i inventory/k1-seungkyua/inventory.ini upgrade-cluster.yml --limit k1-node01

문제없이 업그레이드가 완료되었다.

Every 2.0s: kubectl get nodes                                                                                                                                           Thu Jan  9 16:27:17 2020

NAME          STATUS   ROLES          AGE    VERSION
k1-master01   Ready    master         323d   v1.15.6
k1-master02   Ready    master         323d   v1.15.6
k1-master03   Ready    master         323d   v1.15.6
k1-node01     Ready    ingress,node   323d   v1.15.6
k1-node02     Ready    node           323d   v1.14.3
k1-node03     Ready    node           322d   v1.14.3
k1-node04     Ready    node           323d   v1.14.3
k1-node05     Ready    node           323d   v1.14.3

이제 나머지 노드를 모드 업그레이드를 하면 된다. Kubespray 는 한대씩 순차적으로 업그레이드를 하기 때문에 전체 서비스에는 영향이 없다. (서비스를 여러 pod 로 load balancing 했을 경우에)

 

Posted by Kubernetes Korea co-leader seungkyua@gmail.com

댓글을 달아 주세요

이제는 Kubenretes 가 많이 사용되고 성숙해진 것 같다. KubeCon US 2019 에서도 키노트에 Security 를 강조한 것을 보면 잘 알 수가 있는데, 일반적으로 보안을 강조하기 시작하면 이미 해당 field 는 많이 성숙한 단계라고 봐도 무관하다.

 

그래서 Kubernetes 보안에 관심을 갖고 관련 내용을 찾아봤다. 뭐 별로 내용이 많지는 않지만...

 

1. CIS (Center for Internet Security)

CIS

Kubernetes 관련하여 Benchmark 자료가 나와 있는 사이트. 현재 Kubernetes 버전은 1.15 까지 나와 있다.

 

물론 Linux, Windows 와 같은 OS 부터 Apache, Nginx 와 같은 웹서버에 대한 benchmark 자료도 다운 받을 수 있다. 보안을 다루는 곳이라면 다 알듯...

 

 

2. OPA (Open Policy Agent)

https://github.com/open-policy-agent/gatekeeper

 

open-policy-agent/gatekeeper

Gatekeeper - Policy Controller for Kubernetes. Contribute to open-policy-agent/gatekeeper development by creating an account on GitHub.

github.com

CNCF 프로젝트 중에 하나로, RBAC 으로 권한에 제한을 두었다고 하더라도 해당 권한의 사용자 컨테이너가 탈취당한다면 컨테이너 안에서 Docker 명령이나 Kubectl 명령으로 새로운 자원을 띄우는 것이 가능하다. 이런 비정상 상황에서 자원 생성을 막을 수 있는 것이 OPA의 gatekeeper 이다.

 

 

3. Clair

https://coreos.com/clair/docs/latest/

 

CoreOS

Clair is an open source project for the static analysis of vulnerabilities in appc and docker containers. Read our Clair documentation for easy deployment.

coreos.com

CoreOS 에서 만든 docker 컨테이너의 취약점을 정적으로 분석해주는 오픈소스 프로젝트. 내부적으로 Docker Image Registry 를 사용하고 있다면 사용을 고려해 봐야 한다.

 

 

 

Posted by Kubernetes Korea co-leader seungkyua@gmail.com

댓글을 달아 주세요

Kubernetes 의 모든 metadata 는 etcd 에 저장됩니다. etcd 를 안정적으로 가져가기 위해 3 node 로 cluster 를 구성하는 방법을 사용합니다. 대부분의 문서는 일반적으로 etcd 를 백업 / 복구로 설명하고 있는데, 그 보다 서버 fault 가 나서 etcd 를 1 node 나 2 node 로 임시 운영해야 할 경우에 대해서 설명하고자 합니다.

 

[ etcd 노드 리스트 ]

k2-master01 : etcd1

k2-master02 : etcd2

k2-master03 : etcd3

 

먼저 etcdctl 명령어를 사용하기 쉽게 alias 로 다음과 같이 등록을 해줍니다. 각 노드 마다 endpoint ip 와 인증서 파일은 이름을 적절히 변경해 주어야 합니다.

# vi ~/.bash_aliases

alias etcdctl="export ETCDCTL_API=3; etcdctl --endpoints=[192.168.30.151:2379] --cacert=\"/etc/ssl/etcd/ssl/ca.pem\" --cert=\"/etc/ssl/etcd/ssl/admin-k2-master01.pem\" --key=\"/etc/ssl/etcd/ssl/admin-k2-master01-key.pem\" "


# . ~/.bash_aliases

 

멤버 조회를 하면 다음과 같이 3개 노드가 정상적으로 나타납니다.

# etcdctl --write-out=table member list
+------------------+---------+-------+-----------------------------+-----------------------------+
|        ID        | STATUS  | NAME  |         PEER ADDRS          |        CLIENT ADDRS         |
+------------------+---------+-------+-----------------------------+-----------------------------+
| 683a3374b573e7c7 | started | etcd1 | https://192.168.30.151:2380 | https://192.168.30.151:2379 |
| 6f89416652d7e034 | started | etcd2 | https://192.168.30.152:2380 | https://192.168.30.152:2379 |
| d9c4159e72350017 | started | etcd3 | https://192.168.30.153:2380 | https://192.168.30.153:2379 |
+------------------+---------+-------+-----------------------------+-----------------------------+

 

모든 노드의 etcd 를 stop 하여 3대 모두 장애상황으로 만듭니다.

# systemctl stop etcd

 

3대 노드의 etcd 가 모두 장애로 다운되었고, 빠른 서비스를 위해 etcd1 만 띄운다고 하면, etcd db 에 이미 기존의 3대 cluster member 를 인식하고 있기 때문에 etcd1 만 실행시키는 것을 실패하게 됩니다. 이 때는 기존의 db  (백업 db 가 아닌 운영되고 있었던 db) 를 가지고 restore 를 하여 member 정보를 새롭게 update 할 수 있습니다. 그리고 snapshot 으로 백업된 db 정보가 아니므로 hash check 도 skip 을 해야 합니다.  

 

k2-master01 노드에서 작업

먼저, db 를 복사해 옵니다. (etcd 의 data dir 는 /var/lib/etcd 라고 가정합니다.) 그리고 /var/lib/etcd 디렉토리는 삭제합니다.

# mkdir -p ~/etcd-backup
# cp /var/lib/etcd/member/snap/db ~/etcd-backup/current.db

# rm -rf /var/lib/etcd/

 

복사한 db 를 사용하여 restore 를 하면서, initial-cluster 값으로 member 정보를 덮어씁니다.

# etcdctl snapshot restore ~/etcd-backup/current.db \
--data-dir=/var/lib/etcd \
--initial-advertise-peer-urls=https://192.168.30.151:2380 \
--initial-cluster=etcd1=https://192.168.30.151:2380 \
--initial-cluster-token=k8s_etcd \
--name=etcd1 \
--skip-hash-check

 

새롭게 cluster 를 생성하는 방식으로 etcd1 만 실행하기 위해 옵션 값 중에 ETCD_INITIAL_CLUSTER_STATE=new 와 ETCD_INITIAL_CLUSTER=etcd1=https://192.168.30.151:2380 으로 변경합니다.

# vi /etc/etcd.env

# Environment file for etcd v3.2.26
ETCD_DATA_DIR=/var/lib/etcd
ETCD_ADVERTISE_CLIENT_URLS=https://192.168.30.151:2379
ETCD_INITIAL_ADVERTISE_PEER_URLS=https://192.168.30.151:2380
#ETCD_INITIAL_CLUSTER_STATE=existing
ETCD_INITIAL_CLUSTER_STATE=existing
ETCD_METRICS=basic
ETCD_LISTEN_CLIENT_URLS=https://192.168.30.151:2379,https://127.0.0.1:2379
ETCD_ELECTION_TIMEOUT=5000
ETCD_HEARTBEAT_INTERVAL=250
ETCD_INITIAL_CLUSTER_TOKEN=k8s_etcd
ETCD_LISTEN_PEER_URLS=https://192.168.30.151:2380
ETCD_NAME=etcd1
ETCD_PROXY=off
ETCD_INITIAL_CLUSTER=etcd1=https://192.168.30.151:2380,etcd2=https://192.168.30.152:2380,etcd3=https://192.168.30.153:2380
ETCD_AUTO_COMPACTION_RETENTION=8
ETCD_SNAPSHOT_COUNT=10000

# TLS settings
ETCD_TRUSTED_CA_FILE=/etc/ssl/etcd/ssl/ca.pem
ETCD_CERT_FILE=/etc/ssl/etcd/ssl/member-k2-master01.pem
ETCD_KEY_FILE=/etc/ssl/etcd/ssl/member-k2-master01-key.pem
ETCD_CLIENT_CERT_AUTH=true

ETCD_PEER_TRUSTED_CA_FILE=/etc/ssl/etcd/ssl/ca.pem
ETCD_PEER_CERT_FILE=/etc/ssl/etcd/ssl/member-k2-master01.pem
ETCD_PEER_KEY_FILE=/etc/ssl/etcd/ssl/member-k2-master01-key.pem
ETCD_PEER_CLIENT_CERT_AUTH=True

 

이제 etcd 를 실행하면 이전 데이터를 가지고 1대의 노드로 실행시킬 수 있습니다.

# systemctl start etcd

 

멤버 조회를 해보면 자신만 나오는 것을 알 수 있습니다.

# etcdctl --write-out=table member list
+------------------+---------+-------+-----------------------------+-----------------------------+
|        ID        | STATUS  | NAME  |         PEER ADDRS          |        CLIENT ADDRS         |
+------------------+---------+-------+-----------------------------+-----------------------------+
| 683a3374b573e7c7 | started | etcd1 | https://192.168.30.151:2380 | https://192.168.30.151:2379 |
+------------------+---------+-------+-----------------------------+-----------------------------+

 

etcd 3 node cluster 원상 복구

k2-master01 서버에서 작업

etcd cluster 를 다시 3 node 로 만들기 위해 etcd2 member 를 add 합니다.

# etcdctl member add etcd2 --peer-urls=https://192.168.30.152:2380
Member d8f69cbfe4c8a34f added to cluster a6552752c1542947

ETCD_NAME="etcd2"
ETCD_INITIAL_CLUSTER="etcd1=https://192.168.30.151:2380,etcd2=https://192.168.30.152:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"

 

k2-master02 서버에서 작업

etcd.env 에서 initial cluster 값에 자기 자신인 etcd2 를 추가합니다. initial cluster state 는 existing 으로 그대로 진행합니다.

# vi /etc/etcd.env

ETCD_INITIAL_CLUSTER=etcd1=https://192.168.30.151:2380,etcd2=https://192.168.30.152:2380

etcd2 의  data dir 를 삭제하고 etcd2 ㅇ를 실행합니다.

# rm -rf /var/lib/etcd/
# systemctl start etcd

member 조회를 하면 정상적으로 etcd2 가 추가된 것을 알 수 있습니다.

# etcdctl --write-out=table member list
+------------------+---------+-------+-----------------------------+-----------------------------+
|        ID        | STATUS  | NAME  |         PEER ADDRS          |        CLIENT ADDRS         |
+------------------+---------+-------+-----------------------------+-----------------------------+
| 683a3374b573e7c7 | started | etcd1 | https://192.168.30.151:2380 | https://192.168.30.151:2379 |
| d8f69cbfe4c8a34f | started | etcd2 | https://192.168.30.152:2380 | https://192.168.30.152:2379 |
+------------------+---------+-------+-----------------------------+-----------------------------+

 

k2-master01 서버에서 작업

etcd3 member 를 add 합니다.

# etcdctl member add etcd3 --peer-urls=https://192.168.30.153:2380
Member 63f37d54154a6e23 added to cluster a6552752c1542947

ETCD_NAME="etcd3"
ETCD_INITIAL_CLUSTER="etcd3=https://192.168.30.153:2380,etcd1=https://192.168.30.151:2380,etcd2=https://192.168.30.152:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"

 

k2-master03 서버에서 작업

etcd3 의 data dir 삭제 및 etcd3 를 start 합니다.

# rm -rf /var/lib/etcd
# systemctl start etcd

정상적으로 member 가 추가되었음을 알 수 있습니다.

# etcdctl --write-out=table member list
+------------------+---------+-------+-----------------------------+-----------------------------+
|        ID        | STATUS  | NAME  |         PEER ADDRS          |        CLIENT ADDRS         |
+------------------+---------+-------+-----------------------------+-----------------------------+
| 63f37d54154a6e23 | started | etcd3 | https://192.168.30.153:2380 | https://192.168.30.153:2379 |
| 683a3374b573e7c7 | started | etcd1 | https://192.168.30.151:2380 | https://192.168.30.151:2379 |
| d8f69cbfe4c8a34f | started | etcd2 | https://192.168.30.152:2380 | https://192.168.30.152:2379 |
+------------------+---------+-------+-----------------------------+-----------------------------+

 

 

마지막으로 설정을 변경해야 할 것이 있습니다. 언제든지 etcd 는 restart 를 할 수 있으므로 etcd1 과 etcd2 의 initial cluster state 를 existing 으로 변경하고 initial cluster 의 멤버를 3대로 모두 지정해야 합니다.

 

k2-master02 서버에서 작업

etcd2 옵션의 initial cluster 값에 etcd3 를 포함

# vi /etc/etcd.env

ETCD_INITIAL_CLUSTER=etcd1=https://192.168.30.151:2380,etcd2=https://192.168.30.152:2380,etcd3=https://192.168.30.153:2380

 

k2-master01 서버에서 작업

etcd1  옵션의 initial cluster state 를 existing 으로 변경하고 initial cluster 값에 etcd2 와 etcd3 를 포함

# vi /etc/etcd.env

ETCD_INITIAL_CLUSTER_STATE=existing
ETCD_INITIAL_CLUSTER=etcd1=https://192.168.30.151:2380,etcd2=https://192.168.30.152:2380,etcd3=https://192.168.30.153:2380

 

 

Posted by Kubernetes Korea co-leader seungkyua@gmail.com

댓글을 달아 주세요

Ingress Controller 를 사용하여 같은 도메인에서 subpath 로 웹서비스를 분리하는 방법은 각 서비스마다 Ingress 를 만들고, '/' path 는 무조건 포함시키는 방법입니다. 한가지 단점은 root path '/' 는 처음 만든 Ingress 가 먼저 선점하게 됩니다.

 

물론 이 방법은 '/' 가 경로가 반드시 필요한 서비스가 2개 이상이면 적용할 수 없지만 그런 경우는 없다고 가정합니다.

 

예를 들어 Jupyterlab 은 '/' 경로를 반드시 필요로 합니다. '/jupyter' 를 호출할 때 jupyterlab 이 호출되게 Ingress 의 subpath 를 지정하려면 다음과 같이 작성하면 됩니다.

 

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /
  name: jupyterlab
  namespace: jupyterlab
spec:
  rules:
  - host: jupyterlab.myserver
    http:
      paths:
      - backend:
          serviceName: jupyterlab
          servicePort: 80
        path: /jupyter
  - host: jupyterlab.myserver
    http:
      paths:
      - backend:
          serviceName: jupyterlab
          servicePort: 80
        path: /

이렇게 하면 'http://jupyterlab.myserver/jupyter' 가 호출되거나 'http://jupyterlab.myserver/' 가 호출되어도 jupyterlab 이 잘 연결됩니다.

 

여기에 '/echoserver' 를 subpath 로 연결되게 추가하고 싶으면 새로운 Ingress 를 만들면 됩니다.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /
  name: echoserver
  namespace: jupyterlab
spec:
  rules:
  - host: jupyterlab.myserver
    http:
      paths:
      - backend:
          serviceName: clusterip
          servicePort: 80
        path: /echoserver
  - host: jupyterlab.myserver
    http:
      paths:
      - backend:
          serviceName: clusterip
          servicePort: 80
        path: /

여기서 두번째에 들어가 '/' 는 소용이 없습니다. Nginx 에서는 처음 입력된 순서에 의해서 location 이 결정되기 때문에 'http://jupyterlab.myserver/' 를 호출하면 첫번째 생성한 jupyterlab ingress 가 적용되기 때문입니다. 

 

 

Posted by Kubernetes Korea co-leader seungkyua@gmail.com

댓글을 달아 주세요

웹서비스를 인터넷에 노출시키기 위해서는 Cloud Provider 가 제공하는 Load Balancer 나 On-Prem 에서 Load Balancer 나 Ingress Controller 를 사용해야 합니다. Ingress Controller 를 사용할 때 원래의 Client IP 를 확인하는 방법은 다음과 같습니다.

 

여기서 사용하는 Ingress Controller 와 Pod 의 Web Server 는 Nginx 를 사용했습니다.

1. [ 인터넷 ] ---> [ Ingress Controller ] ---> [ Pod (Web Server) ]

가장 기본적인 방법으로 추가 세팅없이도 바로 original client ip 를 알 수 있습니다.

 

Ingress Controller 를 설치하기 위해 node label 을 세팅합니다.

$ kubectl label --overwrite node k1-node01 node-role.kubernetes.io/ingress=true

그리고 아래의 Ingress Controller yaml 로 설치합니다.

$ vi nginx-controller.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
data:
  enable-underscores-in-headers: "true"

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: tcp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: udp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nginx-ingress-serviceaccount
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: nginx-ingress-clusterrole
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses/status
    verbs:
      - update

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: nginx-ingress-role
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<nginx>"
      # This has to be adapted if you change either parameter
      # when launching the nginx-ingress-controller.
      - "ingress-controller-leader-nginx"
    verbs:
      - get
      - update
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: nginx-ingress-role-nisa-binding
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: nginx-ingress-role
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: nginx-ingress-clusterrole-nisa-binding
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: nginx-ingress-clusterrole
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: ClusterIP
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
    - name: https
      port: 443
      targetPort: 443
      protocol: TCP
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
      annotations:
        prometheus.io/port: "10254"
        prometheus.io/scrape: "true"
    spec:
      serviceAccountName: nginx-ingress-serviceaccount
      hostNetwork: true
      nodeSelector:
        node-role.kubernetes.io/ingress: "true"
      containers:
        - name: nginx-ingress-controller
          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.24.0
          args:
            - /nginx-ingress-controller
            - --configmap=$(POD_NAMESPACE)/nginx-configuration
            - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
            - --udp-services-configmap=$(POD_NAMESPACE)/udp-services
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx
            - --annotations-prefix=nginx.ingress.kubernetes.io
          securityContext:
            allowPrivilegeEscalation: true
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            # www-data -> 33
            runAsUser: 33
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
            - name: https
              containerPort: 443
          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10

Ingress Controller 가 설치 되었으면 echoserver 로 Pod 에서 어떻게 Client IP 를 받는지 확인할 수 있습니다.

echoserver 소스 :  https://kubernetes.io/docs/tutorials/services/source-ip/

 

Using Source IP

 

kubernetes.io

$ kubectl create ns echoserver

$ vi echoserver.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: source-ip-app
  namespace: echoserver
  labels:
    run: source-ip-app
spec:
  replicas: 1
  selector:
    matchLabels:
      run: source-ip-app
  template:
    metadata:
      labels:
        run: source-ip-app
    spec:
      containers:
      - image: k8s.gcr.io/echoserver:1.4
        name: source-ip-app
$ vi echoserver-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: clusterip
  namespace: echoserver
  labels:
    run: source-ip-app
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    run: source-ip-app
  type: ClusterIP

echoserver 는 nginx 서버이며 head 의 값을 출력하는 로직을 가지고 있습니다.

busybox 로 echoserver 출력 내용을 확인해 보겠습니다. 그러기 위해서 먼저, echoserver 의 service ip 를 확인한 후 curl 로 http request 를 호출해 봅니다.

$ kubectl get svc -n echoserver 
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
clusterip   ClusterIP   10.233.35.118   <none>        80/TCP    66s
# kubectl run busybox -it --image=busybox --restart=Never --rm -n echoserver

If you don't see a command prompt, try pressing enter.
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop qlen 1
    link/ipip 0.0.0.0 brd 0.0.0.0
3: gre0@NONE: <NOARP> mtu 1476 qdisc noop qlen 1
    link/gre 0.0.0.0 brd 0.0.0.0
4: gretap0@NONE: <BROADCAST,MULTICAST> mtu 1462 qdisc noop qlen 1000
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
6: eth0@if404: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 5e:8e:b0:19:be:56 brd ff:ff:ff:ff:ff:ff
    inet 10.233.125.19/32 scope global eth0
       valid_lft forever preferred_lft forever

/ # wget -qO - 10.233.35.118
CLIENT VALUES:
client_address=10.233.125.19
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://10.233.35.118:8080/

SERVER VALUES:
server_version=nginx: 1.10.0 - lua: 10001

HEADERS RECEIVED:
connection=close
host=10.233.35.118
user-agent=Wget
BODY:
-no body in request-

pod 에서 service 를 통해 web server 를 호출하면 client_address 에 client ip 가 제대로 출력됩니다. 그러나, Ingress Controller 호출하는 경우에는 다음과 같습니다.

 

Ingress Controller 는 k1-node01 (192.168.30.12) 에 떠있고, k1-node05 (192.168.30.20) 에서 echoserver 를 호출해 보겠습니다.

echoserver 를 위한 Ingress 를 생성합니다.

$ vi echoserver-ingress.yaml 

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
  name: echoserver
  namespace: echoserver
spec:
  rules:
  - host: echoserver.ip
    http:
      paths:
      - backend:
          serviceName: clusterip
          servicePort: 80
        path: /

k1-node05 서버에서 echoserver.ip 도메인을 hosts 파일에 등록하고, echoserver 를 호출합니다.

$ cat /etc/hosts
192.168.30.12    k1-node01    echoserver.ip
192.168.30.20    k1-node05



$ curl -X GET echoserver.ip -H 'Host: echoserver.ip'
CLIENT VALUES:
client_address=192.168.30.12
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://echoserver.ip:8080/

SERVER VALUES:
server_version=nginx: 1.10.0 - lua: 10001

HEADERS RECEIVED:
accept=*/*
host=echoserver.ip
user-agent=curl/7.47.0
x-forwarded-for=192.168.30.20
x-forwarded-host=echoserver.ip
x-forwarded-port=80
x-forwarded-proto=http
x-original-uri=/
x-real-ip=192.168.30.20
x-request-id=15b32d8bb2a8945f0dfebf3fae29c736
x-scheme=http
BODY:
-no body in request-

client_address 에는 Ingress Controller 가 떠있는 서버의 ip 가 들어오고, header 의 x-forwared-for 값에 original client ip 가 출력되는 것을 알 수 있습니다.

 

실제 Ingress Controller 의 echoserver 에 대한 config 값은 다음과 같습니다.

proxy_set_header X-Request-ID           $req_id;
proxy_set_header X-Real-IP              $the_real_ip;
             
proxy_set_header X-Forwarded-For        $the_real_ip;
             
proxy_set_header X-Forwarded-Host       $best_http_host;
proxy_set_header X-Forwarded-Port       $pass_port;
proxy_set_header X-Forwarded-Proto      $pass_access_scheme;
             
proxy_set_header X-Original-URI         $request_uri;
             
proxy_set_header X-Scheme               $pass_access_scheme;
             
# Pass the original X-Forwarded-For
proxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;

Pod 의 nginx 에서 client ip 에 original client ip 를 출력하고 싶을 때는 pod nginx 에 다음과 같은 설정이 필요합니다. (추가 확인 필요)

set_real_ip_from  192.168.30.0/24;
real_ip_header    X-Forwarded-For;

set_real_ip_from 은 ingress controller 가 실행될 수 있는 서버의 cidr 입니다.

 

 

2. [ 인터넷 ] ---> [ Google or Azure LB ] ---> [ Pod (Web Server) ]

Cloud Provider (Google or Azure) 의 LB 를 이용하고 Service 의  LoadBalancer type 으로 웹서비스를 한다면 아래와 같이 Service 의  externalTrafficPolicy 값을 local 로 지정하면 됩니다. local 로 지정하면 LB 에서 바로 Web Server Pod 가 떠 있는 서버로만 접근되기 때문에 다른 서버를 통해서 연결될 때 client ip 가 해당 서버의 ip 로 들어가는 것을 방지해 줍니다.

---
kind: Service
apiVersion: v1
metadata:
  name: example-service
spec:
  ports:
  - port: 8765
    targetPort: 9376
  selector:
    app: example
  type: LoadBalancer
  externalTrafficPolicy: Local

Azure 부분은 한국 MS 의 박인혜 차장께서 알려주셨습니다.

 

3. [ 인터넷 ] ---> [ AWS ELB (classic lb) ] ---> [ Pod (Web Server) ]

Classic LB 에 proxy protocol 을 enable 하고, Service 의 annotations 에 다음과 같이 설정합니다.

# Enable PROXY protocol
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: 'https'
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-west-myarn-long-entry-obfuscated-here

 

4. [ 인터넷 ] ---> [ AWS NLB ] ---> [ Ingress Controller ] ---> [ Pod (Web Server) ]

Ingress Controller 의 config 에 다음과 같이 설정합니다.

use-proxy-protocol: "true"
real-ip-header: "proxy_protocol"

 

AWS NLB 의 각 타겟 그룹에 Proxy Protocol V2 를 설정한 후,  Ingress Controller 의 Service annotations 에 다음과 같이 설정합니다.

# by default the type is elb (classic load balancer).
service.beta.kubernetes.io/aws-load-balancer-type: nlb
# Enable PROXY protocol
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: 'tcp'

 

※ AWS 와 Google 의 경우에는 테스트 해보지 못했습니다. 

Posted by Kubernetes Korea co-leader seungkyua@gmail.com

댓글을 달아 주세요

Ingress Controller 를 활용한 웹서비스에 TLS 를 적용하는 방법 중 가장 쉬운 방법은 Ingress Controller 에 TLS 를 적용하는 방법입니다. 이 방법은 client 와 Ingress Controller 까지 https 로 통신하고 Ingress Controller 와 web server 와는 http 로 통신하게 됩니다.

아래의 방법은 Ingress Controller 는 미리 설치되어 있다고 가정합니다.

 

먼저, letsencrypt 로 접속하여 3개월간 무료로 인증서를 받는 방법을 알아봅니다.

 

1. 필요 패키지 설치 (Ubuntu 16.04 기준)

# apt-get update
# apt-get install software-properties-common
# add-apt-repository universe
# sudo add-apt-repository ppa:certbot/certbot
# sudo apt-get update
# sudo apt-get install certbot python-certbot-nginx

 

2. 인증서 다운로드

중요한 것은 인터넷에서 해당 도메인으로 로컬 서버에 접속이 되어야 하며 (Inbound 가능) standalone 을 사용할 때 로컬에 80 포트는 unbind 되어 있어야 합니다. 아래와 같은 경우는 외부에서 cloudnativeday.kr, www.cloudnativeday.kr, test.cloudnativeday.kr 도메인이 갖는 ip address 가 내 로컬 서버여야 한다는 의미입니다. 정확히는 해당 ip address 로 외부에서 내 서버로 연결이 가능해야 합니다.

# certbot certonly --standalone --cert-name cloudnativeday.kr -d cloudnativeday.kr,www.cloudnativeday.kr,test.cloudnativeday.kr

 

인증서 발급이 완료되면 다음과 같은 디렉토리에 인증서가 생성되어 보입니다. archive 디렉토리와 심볼릭 링크가 있는 live 디렉토리가 있는데 live 디렉토리를 활용합니다. 그리고, crt 는 fullchain.pem 을, key 는 private.pem 을 사용합니다.

 

# ls -al /etc/letsencrypt/archive/cloudnativeday.kr/
total 24
drwxr-xr-x 2 root root 4096 Apr  7 21:31 .
drwx------ 3 root root 4096 Apr  7 21:31 ..
-rw-r--r-- 1 root root 1931 Apr  7 21:31 cert1.pem
-rw-r--r-- 1 root root 1647 Apr  7 21:31 chain1.pem
-rw-r--r-- 1 root root 3578 Apr  7 21:31 fullchain1.pem
-rw------- 1 root root 1704 Apr  7 21:31 privkey1.pem


# ls -al /etc/letsencrypt/live/cloudnativeday.kr/
total 12
drwxr-xr-x 2 root root 4096 Apr  7 21:31 .
drwx------ 3 root root 4096 Apr  7 21:31 ..
lrwxrwxrwx 1 root root   41 Apr  7 21:31 cert.pem -> ../../archive/cloudnativeday.kr/cert1.pem
lrwxrwxrwx 1 root root   42 Apr  7 21:31 chain.pem -> ../../archive/cloudnativeday.kr/chain1.pem
lrwxrwxrwx 1 root root   46 Apr  7 21:31 fullchain.pem -> ../../archive/cloudnativeday.kr/fullchain1.pem
lrwxrwxrwx 1 root root   44 Apr  7 21:31 privkey.pem -> ../../archive/cloudnativeday.kr/privkey1.pem
-rw-r--r-- 1 root root  692 Apr  7 21:31 README

 

 

인증서를 성공적으로 생성하고 나면, Ingress Controller 에서 사용하기위해서 인증서를 secret tls 타입으로 생성합니다.

 

3. secret tls 생성

# kubectl create secret tls cloudnativeday-certs --key /etc/letsencrypt/live/cloudnativeday.kr/privkey.pem --cert /etc/letsencrypt/live/cloudnativeday.kr/fullchain.pem --namespace cloudnative

타입은 secret tls 로 하고, key 는 private.pem 을, cert 는  fullchain.pem 을 사용합니다.

 

 

4. tls ingress 생성

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
  name: cloudnative-kr
  namespace: cloudnative
spec:
  tls:
  - secretName: cloudnativeday-certs
    hosts:
    - cloudnativeday.kr
    - www.cloudnativeday.kr
  rules:
  - host: cloudnativeday.kr
    http:
      paths:
      - backend:
          serviceName: nginx-cloudnative
          servicePort: 80
        path: /
  - host: www.cloudnativeday.kr
    http:
      paths:
      - backend:
          serviceName: nginx-cloudnative
          servicePort: 80
        path: /

http 로 들어오면 자동으로 https 로 redirect 됩니다. 왜냐하면 기본 값이 nginx.ingress.kubernetes.io/ssl-redirect: "true" 이기 때문입니다. 일반 http 와 다른 부분은 tls 부분입니다. secret 과 인증서가 적용될 도메인명을 적으면 됩니다.

 

Posted by Kubernetes Korea co-leader seungkyua@gmail.com

댓글을 달아 주세요

Kubernetes 는 RBAC 이 디폴트 접근 제어 권한으로 User account 와 RBAC 을 적절히 사용하면 효과적인 접근 제어를 할 수 있습니다.

예를 들어, Kubernetes Cluster 에 접근할 수 있는 사용자 seungkyua 를 새로 만들고 해당 사용자는 seungkyua 네임스페이스에만 접근 가능하게 설정하고 싶은 경우가 있을 수 있습니다.

또한 seungkyua 네임스페이스 접근 안에서도 pods, replicasets, deployments, services 만 생성, 조회, 수정, 삭제권한만 주고 싶을 때도 있습니다.

 

이러한 세팅은 아래와 같은 절차를 거쳐야 합니다. 물론 Kubernetes 는 RBAC 사용으로 세팅되어 있다고 가정합니다.

  1. 신규 namespace 생성
  2. User account 생성 (client key 와 client crt 생성)
  3. 새로운 namespace 에 제한적으로 접근할 수 있는 권한 생성 (role 생성)
  4. 3번의 새로운 User account 와 3 번의 role 을 연결 (rolebinding 생성)
  5. kubectl 이 사용할 새로운 config context 를 만들고 연결

 

1. 신규 naemspace 생성

# kubectl create namespace seungkyua

 

2. 새로운 User account 생성

  • client private key 생성 
# openssl genrsa -out seungkyua.key 2048

 

  • crt 생성을 위한 csr 생성
# openssl req -new -key seungkyua.key -subj "/CN=seungkyua/O=Kubernetes Korea Group" -out seungkyua.csr

 

  • client cert 파일 생성
# openssl x509 -req -in seungkyua.csr -CA /etc/kubernetes/ssl/ca.crt -CAkey /etc/kubernetes/ssl/ca.key -CAcreateserial -out seungkyua.crt -days 10000

 

3. 새로운 namespace 로 접근 role 설정

# vi role.yaml

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: seungkyua-manager
  namespace: seungkyua
rules:
  - apiGroups:
      - ""            # core api
      - "extensions"
      - "apps"
    resources:
      - "deployments"
      - "replicasets"
      - "pods"
      - "services"
    verbs:
      - "get"
      - "list"
      - "watch"
      - "create"
      - "update"
      - "patch"
      - "delete"


# kubectl create -f role.yaml

 

4. User 와 Role 을 연결 (rolebinding 생성)

# vi rolebinding.yaml

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: seungkyua-manager-rolebinding
  namespace: seungkyua
subjects:
  - kind: User         # User or ServiceAccount
    name: seungkyua
roleRef:
  kind: Role
  name: seungkyua-manager
  apiGroup: rbac.authorization.k8s.io
  

# kubectl create -f rolebinding.yaml

 

5. Kubectl config context 생성

  • 인증을 위한 사용자 정보 등록 (key, crt 로 접속)
# kubectl config set-credentials seungkyua --client-certificate=/root/ahnsk/private-certs/seungkyua.crt --client-key=/root/ahnsk/private-certs/seungkyua.key --embed-certs=true

 

  • context 생성
# kubectl config get-clusters NAME cluster.local # kubectl config set-context seungkyua@k1 --cluster=cluster.local --user=seungkyua --namespace=seungkyua

 

  • kubernetes 접속
# kubectl config get-contexts
CURRENT   NAME                             CLUSTER         AUTHINFO           NAMESPACE
*         kubernetes-admin@cluster.local   cluster.local   kubernetes-admin   
          seungkyua@k1                     cluster.local   seungkyua 


# kubectl config use-context seungkyua@k1


# kubectl config get-contexts
CURRENT   NAME                             CLUSTER         AUTHINFO           NAMESPACE
          kubernetes-admin@cluster.local   cluster.local   kubernetes-admin   
*         seungkyua@k1                     cluster.local   seungkyua          seungkyua

 

Kubernetes 에 접속하여 해당 계정이 해당 네임스페이스에만 접속 권한이 있는지 확인

# kubectl get pods

No resources found.



# kubectl get pods -n defaults

Error from server (Forbidden): pods is forbidden: User "seungkyua" cannot list resource "pods" in API group "" in the namespace "defaults"

 

 

Posted by Kubernetes Korea co-leader seungkyua@gmail.com

댓글을 달아 주세요