반응형

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 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 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 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 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 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 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 seungkyua@gmail.com
,
반응형


https://github.com/seungkyua/my-kubespray

my-kubespray

You can easily add your custom logic without modifying kubespray.

The my-kubespray has serveral parameters to override and can deploy ceph rbd provisioner into namespace.

Prerequisites

  • All servers must have the same user, and the user must set root permissions to NOPASSWD in sudo.

  • In the deployment node (or master 1 node), you need to enable ssh access to all other servers (including localhost) without a password to the corresponding account.

  • Register all cluster servers in the /etc/hosts file.

  • Time synchronization between servers with ntp.

  • Enable network restart with systemctl restart network.service .

  • Disable firewall such as selinux or ufw.

  • swapoff.

  • Install python 2.7.x in ansible runner node.

Quick Start

To deploy the cluster you can use :

# Download kubespray and my-kubespray
$ git clone https://github.com/kubernetes-sigs/kubespray.git
$ git clone https://github.com/seungkyua/my-kubespray.git

# Change directory into my-kubespray
$ cd my-kubespray

# Install dependencies from ``requirements.txt``
$ sudo pip install -r ../kubespray/requirements.txt

# Copy ``inventory/k2-seungkyua`` as ``inventory/mycluster``
$ cp -rfp inventory/k2-seungkyua inventory/mycluster

# Update Ansible inventory file
$ vi inventory/mycluster/hosts.ini
[all]
k2-master01 ip=192.168.30.151
k2-master02 ip=192.168.30.152
k2-master03 ip=192.168.30.153
k2-ctrl01 ip=192.168.30.154
k2-ctrl02 ip=192.168.30.155
k2-ctrl03 ip=192.168.30.156
k2-cn01 ip=192.168.30.157

[etcd]
k2-master01
k2-master02
k2-master03

[kube-master]
k2-master01
k2-master02
k2-master03

[kube-node]
k2-ctrl01
k2-ctrl02
k2-ctrl03
k2-cn01

[k8s-cluster:children]
kube-node
kube-master

# Review and change parameters under ``inventory/mycluster/group_vars/k8s-cluster.yml``
$ cat inventory/mycluster/group_vars/k8s-cluster.yml
populate_inventory_to_hosts_file: false
override_system_hostname: false
helm_enabled: true
etcd_memory_limit: 8192M
kubeconfig_localhost: true
kubectl_localhost: true
ipip_mode: Never
calico_ip_auto_method: "can-reach=8.8.8.8"
kubeadm_enabled: true
docker_insecure_registries:
- seungkyua:5000
dashboard_enabled: true
local_volume_provisioner_enabled: true
ingress_nginx_enabled: true
ingress_nginx_host_network: true
ingress_nginx_nodeselector:
node-role.kubernetes.io/ingress: true
ceph_version: mimic
storageclass_name: rbd
monitors: 192.168.30.23:6789,192.168.30.24:6789,192.168.30.25:6789
admin_token: QBTEBmRVVlh5UmZoTlJBQTMyZTh6Qk5uajV1VElrMDJEbWFwWmc9WA==
user_secret_namespace: default
pool_name: kubes
user_id: kube
user_token: QEFESG9CcFlpZ0o3TVJBQTV2eStjbDM5RXNLcFkzQyt0WEVHckE9WA==

# Run Ansible Playbook to deploy kubernetes cluster
$ ansible-playbook -b -f 30 -i inventory/mycluster/hosts.ini ../kubespray/cluster.yml

# Run Ansible Playbook to deploy ceph rbd provisioner
$ ansible-playbook -b -f 30 -i inventory/mycluster/hosts.ini storage.yml

Miscellaneous

After installation, you can find the artifacts which are kubectl and admin.conf in inventory/mycluster/artifacts directorin on deploy node(ansible runner).

$ ls -al inventory/mycluster/artifacts
total 173620
drwxr-x--- 2 seungkyua seungkyua     4096 Feb 22 05:25 .
drwxrwxr-x 4 seungkyua seungkyua     4096 Feb 22 02:38 ..
-rw-r----- 1 seungkyua seungkyua     5449 Feb 22 05:25 admin.conf
-rwxr-xr-x 1 seungkyua seungkyua 177766296 Feb 22 05:25 kubectl
-rwxr-xr-x 1 seungkyua seungkyua       65 Feb 22 05:25 kubectl.sh

If desired, copy admin.conf to ~/.kube/config and kubectl to /usr/local/bin/kubectl

$ mkdir -p ~/.kube
$ cp inventory/mycluster/artifacts/admin.conf ~/.kube/config
$ sudo cp inventory/mycluster/artifacts/kubectl /usr/local/bin/kubectl



반응형
Posted by seungkyua@gmail.com
,
반응형

Kubernetes Node 가 NotReady 인 경우 해결을 위해서 제일 먼저 해당 노드의 syslog 를 확인 하는 것이 좋다.


## Node 의 syslog 확인 (ubuntu 인 경우)
# vi /etc/log/syslog

Feb 20 12:14:49 k1-node01 kubelet[31100]: E0220 12:14:49.401890   31100 raw.go:146] Failed to watch directory "/sys/fs/cgroup/devices/system.slice": inotify_add_watch /sys/fs/cgroup/devices/system.slice/run-ref5f878051f440b98dfd9bd843a01b58.scope: no space left on device
Feb 20 12:14:49 k1-node01 kubelet[31100]: F0220 12:14:49.401909   31100 kubelet.go:1369] Failed to start cAdvisor inotify_add_watch /sys/fs/cgroup/devices/system.slice/run-ref5f878051f440b98dfd9bd843a01b58.scope: no space left on device
Feb 20 12:14:49 k1-node01 kubelet[31100]: E0220 12:14:49.640153   31100 kubelet.go:2266] node "k1-node01" not found
Feb 20 12:14:49 k1-node01 systemd[1]: kubelet.service: Main process exited, code=exited, status=255/n/a
Feb 20 12:14:49 k1-node01 systemd[1]: kubelet.service: Unit entered failed state.
Feb 20 12:14:49 k1-node01 systemd[1]: kubelet.service: Failed with result 'exit-code'.


위와 같이 inotify_add_watch 에서 no space left on device 로 에러가 날 경우에 sysctl 로 값을 늘려줘야 한다.

## fs.inotify.max_user_watches 값을 늘려줌
# vi /etc/sysctl.d/99-sysctl.conf

net.ipv4.ip_forward=1
net.ipv4.ip_local_reserved_ports=30000-32767
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-arptables=1
net.bridge.bridge-nf-call-ip6tables=1
fs.inotify.max_user_watches=1048576


해당 값을 적용하고 나서 kubelet 이 activating 상태이거나 failed 상태인 경우가 많으므로 아래와 같이 kubelet 을 재 시작해 준다.

## sysctl 로 적용 및 kubelet 재실행

# sysctl -p
# sudo systemctl restart kubelet 






반응형
Posted by seungkyua@gmail.com
,
반응형

kubernetes 에서 ceph rbd provisioner 활용 방법입니다.


1. 먼저 rbd provisioner 가 사용할 rbac 권한 yaml 파일을 만듭니다.
# vi rbd-rbac.yaml
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: rbd-provisioner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
  - apiGroups: [""]
    resources: ["services"]
    resourceNames: ["kube-dns","coredns"]
    verbs: ["list", "get"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: rbd-provisioner
subjects:
  - kind: ServiceAccount
    name: rbd-provisioner
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: rbd-provisioner
  apiGroup: rbac.authorization.k8s.io

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: rbd-provisioner
  namespace: kube-system

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: rbd-provisioner
  namespace: kube-system
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get"]
- apiGroups: [""]
  resources: ["endpoints"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: rbd-provisioner
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: rbd-provisioner
subjects:
- kind: ServiceAccount
  name: rbd-provisioner
  namespace: kube-system 

# kubectl create -f rbd-rbac.yaml 



2. kube-system 네임스페이스로 admin secret 과 user secret 을 생성한다.
# ceph auth get client.admin 2>&1 |grep "key = " |awk '{print  $3'} |xargs echo -n > /tmp/secret.admin
# kubectl create secret generic ceph-secret-admin --type=kubernetes.io/rbd --from-file=/tmp/secret.admin --namespace=kube-system

# ceph auth get-key client.kube > /tmp/secret.user
# kubectl create secret generic ceph-secret-user --type=kubernetes.io/rbd --from-file=/tmp/secret.user --namespace=kube-system 




3. rbd provisioner 가 사용할 storage class yaml 파일을 만듭니다.
pool 이름과 userId 를  정확히 입력해야 합니다.

# vi rbd-storageclass.yaml

---
apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
  name: "rbd"
  annotations:
    storageclass.beta.kubernetes.io/is-default-class: "true"
provisioner: ceph.com/rbd
reclaimPolicy: Delete
parameters:
  monitors: "192.168.30.23:6789,192.168.30.24:6789,192.168.30.25:6789"
  adminId: "admin"
  adminSecretName: "ceph-secret-admin"
  adminSecretNamespace: "kube-system"
  pool: "kubes"
  userId: "kube"
  userSecretName: "ceph-secret-user"
  userSecretNamespace: "kube-system"
  imageFormat: "2"
  imageFeatures: "layering" 


# kubectl create -f rbd-storageclass.yaml 



4. rbd provisioner 를 deployment 타입 yaml 로 생성합니다.
# vi deployment-rbd-provisioner.yaml

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rbd-provisioner
  namespace: kube-system
  labels:
    app: rbd-provisioner
    version: v2.1.1-k8s1.11
spec:
  replicas: 2
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: rbd-provisioner
      version: v2.1.1-k8s1.11
  template:
    metadata:
      labels:
        app: rbd-provisioner
        version: v2.1.1-k8s1.11
    spec:
      priorityClassName: system-cluster-critical
      serviceAccount: rbd-provisioner
      containers:
        - name: rbd-provisioner
          image: quay.io/external_storage/rbd-provisioner:v2.1.1-k8s1.11
          imagePullPolicy: IfNotPresent
          env:
            - name: PROVISIONER_NAME
              value: ceph.com/rbd
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
          command:
            - "/usr/local/bin/rbd-provisioner"
          args:
            - "-id=${POD_NAME}" 


# kubectl create -f deployment-rbd-provisioner.yaml 




5. 이제 default 네임스페이스에 테스트를 해 보겠습니다.
먼저 default 네임스페이스에 user secret 을 생성합니다. (rbd provisioner 를 사용하고자 하는 네임스페이스에는 user secret 을 만들어 줘야 합니다.)
# ceph auth get-key client.kube > /tmp/secret.user
# kubectl create secret generic ceph-secret-user --type=kubernetes.io/rbd --from-file=/tmp/secret.user --namespace=default


6. 테스트용 pod 생성.

# vi rbd-test.yaml

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: rbd-test
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: rbd
  resources:
    requests:
      storage: 1Gi

---
apiVersion: v1
kind: Pod
metadata:
  name: rbd-test
  namespace: default
spec:
  containers:
  - name: pod-test
    image: gcr.io/google_containers/busybox:1.24
    command:
    - "/bin/sh"
    args:
    - "-c"
    - "touch /mnt/SUCCESS && exit 0 || exit 1"
    volumeMounts:
    - name: pvc
      mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
  - name: pvc
    persistentVolumeClaim:
      claimName: rbd-test 



# kubectl create -f rbd-test.yaml 





반응형
Posted by seungkyua@gmail.com
,