최근 Containter Orchestration 관련된 작업을 진행하고 있는데, 초기 테스트 수준의 클러스터 구성이나 솔루션의 기능 확인/검증시에 사용할 Web Server 기능을 하는 컨테이너를 주로 nginx 베이스 이미지를 사용 하고 있는데,  특정 용도에 맞게 html  파일을 수정해 주어야 하는 등 귀찮은 일 들이 많다. 도커 이미지 크기도 100MB 이상이어서 생각보다 크고... 그래서 생각한 것이 "내 맘대로 주무를 수 있는 컨테이너형 작은 웹서버" 를 만들고 언제든 사용할 수 있게 해 보자는 생각이 들었다.

 

 


어떻게 하면 작고 가벼운 웹서버를 만들고 필요할 때 언제든 사용할 수 있는가?


  • 베이스 이미지가 작아야 하므로 busybox 이미지를 쓴다(1 MB +)
  • 웹서버의 하위 페이지에 따라 특정 메시지나 정보를 보여 주는 기능을 간단하게 구현하고, 편하게 다른 기능을 추가하기 위해 go 또는 node.js를 활용한다
  • 웹서버는 실행 인자로 전달하는 TCP port 번호와 동적으로 바인딩되어야 한다(kubernetes 등에서 동적 할당 가능하도록)
  • 만들어진 이미지를 Docker Hub(hub.docker.com)에 저장한다



사전 준비 작업(Prerequisites)


  • hub.docker.com 가입, Account 확보
  • 작업 서버(PC, 가상머신 등)에 Docker engine 설치
  • go 언어 개발에 대한 최소한의 기본 지식



Go 개발 환경 설정(CentOS 7 기준) & 테스트 코딩(helloworld)


* go 바이너리를 다운로드 받고 로그인 계정에 맞게 설치/설정

[root@docker01 ~]# mkdir tmp && cd tmp

[root@docker01 tmp]# curl -LO https://storage.googleapis.com/golang/go1.7.linux-amd64.tar.gz

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                 Dload  Upload   Total   Spent    Left  Speed

100 77.7M  100 77.7M    0     0  2430k      0  0:00:32  0:00:32 --:--:-- 2437k


[root@docker01 tmp]# sudo tar -C /usr/local -xvzf go1.7.linux-amd64.tar.gz


[root@docker01 tmp]# mkdir -p ~/projects/{bin,pkg,src}

[root@docker01 tmp]# sudo vi /etc/profile.d/path.sh

...

export PATH=$PATH:/usr/local/go/bin

[root@docker01 tmp]# vi ~/.bash_profile

...

export GOBIN="$HOME/projects/bin"

export GOPATH="$HOME/projects/src"

[root@docker01 tmp]# source /etc/profile && source ~/.bash_profile

[root@docker01 tmp]# echo $GOPATH && echo $GOBIN

/root/projects/src

/root/projects/bin


* 모든 개발 작업의 시작, Hello World 를 만들고 확인

[root@docker01 ~]# cd $GOPATH

[root@docker01 src]# vi hello.go

package main

import "fmt"

func main() {

  fmt.Printf("Hello, world!!!\n")

}


[root@docker01 src]# go install $GOPATH/hello.go

[root@docker01 src]# $GOBIN/hello

Hello, world!!!



Tiny Web Server 코딩


[root@docker01 src]# vi tiny-goweb.go

/*
Tiny Web server for testing k8s cluster, by Bryan Lee, 2017-08-31
*/

// A tiny web server for viewing the environment kubernetes creates for your
// containers. It exposes the filesystem and environment variables via http
// server.
//
// Modified from explorer tiny web server of k8s team.

package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
)

const (
        // Name of the application
        Name = "Tiny Webserver"
        // Version of the application
        Version = "0.7"
	// Default port
	DefaultPort = 8888
)

func main() {

	var (
		portNum = flag.Int("port", DefaultPort, "Port number to serve at.")
	)

	flag.Parse()

	// Getting hostname of Docker node or Podname
	podname, err := os.Hostname()
	if err != nil {
		log.Fatalf("Error getting hostname: %v", err)
	}

	links := []struct {
		link, desc string
	}{
		{"/", "Default landing page"},
		{"/info", "Show version & usage"},
		{"/fs", "Complete file system as seen by this container."},
		{"/env", "Environment variables as seen by this container."},
		{"/podname", "Podname or Hostname as seen by this container."},
		{"/healthz", "Just respond 200 ok for health checks"},
		{"/quit", "Cause this container to exit."},
	}

	// Handlers
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "<b> Pod/Hostname: %s Port: %d</b><br/><br/>", podname, *portNum)
	})

	http.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "<b> Name: %s Version: %s Port: %d</b><br/><br/>", Name, Version, *portNum)
		fmt.Fprintf(w, "<b> Kubernetes environment explorer usage</b><br/><br/>")
		for _, v := range links {
			fmt.Fprintf(w, `<a href="%v">%v: %v</a><br/>`, v.link, v.link, v.desc)
		}
	})

	http.Handle("/fs", http.StripPrefix("/fs", http.FileServer(http.Dir("/"))))
	http.HandleFunc("/env", func(w http.ResponseWriter, r *http.Request) {
		for _, v := range os.Environ() {
			fmt.Fprintf(w, "%v\n", v)
		}
	})
	http.HandleFunc("/podname", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, podname)
	})
	http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
                w.WriteHeader(200)
		fmt.Fprintf(w, "ok")
	})
	http.HandleFunc("/quit", func(w http.ResponseWriter, r *http.Request) {
		os.Exit(0)
	})

	// Start and listen
	go log.Fatal(http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", *portNum), nil))

	select {}
}



* 컴파일 & 실행(^C 입력시 종료)

[root@docker01 src]# go install $GOPATH/tiny-goweb.go

[root@docker01 src]# $GOBIN/tiny-goweb

^C


* 접속 & 테스트(새로운 터미널을 열어 curl 접속 테스트)

[root@docker01 ~]# curl http://localhost:8888/

<b> Pod/Hostname: docker01 Port: 8888</b><br/><br/>

[root@docker01 ~]# curl http://localhost:8888/quit

curl: (52) Empty reply from server

* 위의 이전에 접속했던 터미널에서 ^C 입력 또는 /quit PATH로 URL 을 보내면 tiny-goweb 웹서버는 종료됨



Docker 이미지 생성 & 업로드(Push)


* entrypoint.sh 스크립트는 busybox Docker 이미지 내에 복사되며, 컨테이너 인스턴스가 기동되면 자동으로 실행된다. 

* $PORT_ARGS 는 뒤에 가서 Kubernetes 에서 정의하는 YAML 내에서 동적으로 정의 가능한 port 번호이며, tiny-goweb 컨테이너(웹서버)에 바인딩 되는 포트 번호를 컨테이너 마다 동적을 다르게 바꾸어 쓰기 위한 환경 변수이다.

* build.sh 를 실행하면 Docker Hub 에 업로드(Push) 까지 수행되므로, docker login 부터 우선 실행해 두어야 한다

[root@docker01 kubeweb]# docker login -u drlee001

Password: 

Login Succeeded


[root@docker01 ~]# mkdir -p ~/docker-image-works/kubeweb && cd docker-image-works/kubeweb


[root@docker01 kubeweb]# vi entrypoint.sh 

#!/bin/sh
/home/tiny-goweb $PORT_ARGS

[root@docker01 kubeweb]# vi Dockerfile 

FROM busybox

MAINTAINER Bryan Lee, <username@maildomain.com> 

COPY ./tiny-goweb /home/
COPY ./entrypoint.sh /

RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

[root@docker01 kubeweb]# vi build.sh 

#!/bin/bash
GOOS=linux GOARCH=386 go build ~/projects/src/tiny-goweb.go
docker build -t kubeweb .
docker tag kubeweb drlee001/kubeweb
docker push drlee001/kubeweb

[root@docker01 kubeweb]# chmod +x build.sh

[root@docker01 kubeweb]# ./build.sh 

Sending build context to Docker daemon     5 MB

Step 1 : FROM busybox

 ---> d20ae45477cb

Step 2 : MAINTAINER Bryan Lee, <username@maildomain.com>

 ---> Using cache

 ---> 0c7859cc0623

Step 3 : COPY ./tiny-goweb /home/

 ---> Using cache

 ---> 8c9989def6df

Step 4 : COPY ./entrypoint.sh /

 ---> e5c99ca98558

Removing intermediate container 755cb7c50807

Step 5 : RUN chmod +x /entrypoint.sh

 ---> Running in 1e3a487fef07

 ---> 1901d5bce610

Removing intermediate container 1e3a487fef07

Step 6 : ENTRYPOINT /entrypoint.sh

 ---> Running in bf5e075f42a0

 ---> 3d2a98acacf1

Removing intermediate container bf5e075f42a0

Successfully built 3d2a98acacf1

The push refers to a repository [docker.io/drlee001/kubeweb]

3baba6a0b530: Pushed 

1ded66f9f97b: Pushed 

c5f7233ef647: Layer already exists 

6a749002dd6a: Layer already exists 

latest: digest: sha256:836f0b1a32dcf0f3db021f92c80fbf90a95dc23c5fdb9abd231a11255a724e63 size: 1152

[root@docker01 kubeweb]# ls -l

total 4896

-rwxr-xr-x. 1 root root     163  9월  8 15:20 build.sh

-rw-r--r--. 1 root root     166  9월  5 00:05 Dockerfile

-rw-r--r--. 1 root root      38  9월  8 15:25 entrypoint.sh

-rwxr-xr-x. 1 root root 4993580  9월  8 15:25 tiny-goweb



업로드된 Docker 이미지로 컨테이너 실행 & 확인 과정


[root@docker01 kubeweb]# docker images | grep kubeweb

drlee001/kubeweb                                         latest              3d2a98acacf1        57 minutes ago      6.123 MB

kubeweb                                                  latest              3d2a98acacf1        57 minutes ago      6.123 MB

[root@docker01 kubeweb]# docker rmi kubeweb

Untagged: kubeweb:latest

Untagged: drlee001/kubeweb@sha256:836f0b1a32dcf0f3db021f92c80fbf90a95dc23c5fdb9abd231a11255a724e63

[root@docker01 kubeweb]# docker rmi drlee001/kubeweb

Untagged: drlee001/kubeweb:latest

Deleted: sha256:3d2a98acacf129b7c2760a0a883603cf223d91b3ccd591d2428592b079e08f0f

Deleted: sha256:1901d5bce6109f73f14789d56d8a44532cc7bb47345750235b8846867700d823

Deleted: sha256:59c60b224638d13fb4fe8b0296fc93bb81f8b7370eec952410ca8276c1bd8e44

Deleted: sha256:e5c99ca98558d159e71ca4ae2fa1be79adf6c3b999b0cb59dfb6d85d40578a5b

Deleted: sha256:100b7cebaa91325a463e2205fa5b3e4cb8cf930212ac3dda86f0b110f6dffa86

Deleted: sha256:8c9989def6df01e86a85e350e57b389c616c6cdb7253322852ca51de4f6b446e

Deleted: sha256:d9dcde924c64da0a5c4c8457fdc721999542a1d1c640c79718d42d58278c3b88

Deleted: sha256:0c7859cc0623f4c9543e54fab31749438046001018e30799bc1bcb4b5fee1e1c

* 확인을 위해, 현재 생성된 Docker 이미지를 삭제한 후, 업로드 된 이미지로 컨테이너를 생성하고 확인한다


* Docker Hub 에 업로드된 이미지로 컨테이너를 실행한다(run = pull & start)

[root@docker01 kubeweb]# docker run -it -d --name kubeweb-test -p 8888:8888 drlee001/kubeweb

Unable to find image 'drlee001/kubeweb:latest' locally

Trying to pull repository docker.io/drlee001/kubeweb ... 

latest: Pulling from docker.io/drlee001/kubeweb

add3ddb21ede: Already exists 

3fec93edb3ab: Pull complete 

814172af256c: Pull complete 

c9fc1f897d75: Pull complete 

Digest: sha256:836f0b1a32dcf0f3db021f92c80fbf90a95dc23c5fdb9abd231a11255a724e63

cab68320b62a6a496d55af83bf345e2489d58681d8d5ae43269162cedcfbd81c

[root@docker01 kubeweb]# docker ps | grep kubeweb-test

cab68320b62a        drlee001/kubeweb                                                                                                                 "/entrypoint.sh"         49 seconds ago      Up 48 seconds       0.0.0.0:8888->8888/tcp   kubeweb-test


* 기동된 kubeweb-test 컨테이너(웹서버)로 curl 접속 테스트

[root@docker01 ~]# curl http://localhost:8888/

<b> Pod/Hostname: cab68320b62a Port: 8888</b><br/><br/>


[root@docker01 ~]# curl http://localhost:8888/info

<b> Name: Tiny Webserver Version: 0.7 Port: 8888</b><br/><br/><b> Kubernetes environment explorer usage</b><br/><br/><a href="/">/: Default landing page</a><br/><a href="/info">/info: Show version & usage</a><br/><a href="/fs">/fs: Complete file system as seen by this container.</a><br/><a href="/env">/env: Environment variables as seen by this container.</a><br/><a href="/podname">/podname: Podname or Hostname as seen by this container.</a><br/><a href="/healthz">/healthz: Just respond 200 ok for health checks</a><br/><a href="/quit">/quit: Cause this container to exit.</a><br/>



Kubernetes Cluster 내에서 kubeweb 컨테이너 기동 & 테스트


* Kubernetes 클러스터에서 컨테이너 정상 기동과 접속을 확인하기 위해 Deployment와 Service 를 정의하고 생성한다

[root@kubemaster kubeweb-test]# vi kubeweb-dep-svc.yaml 

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: tiny-goweb-deploy-test
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: tiny-goweb
    spec:
      containers:
      - name: tiny-goweb
        image: drlee001/kubeweb
        env:
        - name: PORT_ARGS
          value: "--port=7777"
        ports:
        - containerPort: 7777
          name: web-port
          protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: tiny-goweb-svc-test
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: web-port
  selector:
    app: tiny-goweb
  type: NodePort


[root@kubemaster kubeweb-test]# kubectl create -f kubeweb-dep-svc.yaml 

deployment "tiny-goweb-deploy-test" created

service "tiny-goweb-svc-test" created


[root@kubemaster kubeweb-test]# kubectl get pods

NAME                                      READY     STATUS    RESTARTS   AGE

...

tiny-goweb-deploy-test-1854448180-pqnp7   1/1       Running   0          43s

tiny-goweb-deploy-test-1854448180-w32j7   1/1       Running   0          43s


[root@kubemaster ~]# kubectl get svc

[root@kubemaster kubeweb]# kubectl get svc

...

tiny-goweb-svc-test        10.130.131.215   <nodes>       80:30149/TCP



* curl 로 접속 테스트 수행

[root@kubemaster ~]# curl http://10.255.10.171:30149

<b> Pod/Hostname: tiny-goweb-deploy-test-1854448180-w32j7 Port: 7777</b><br/><br/>


[root@kubemaster ~]# curl http://10.255.10.171:30149

<b> Pod/Hostname: tiny-goweb-deploy-test-1854448180-sfxzf Port: 7777</b><br/><br/>[root@kubemaster ~]# 

[root@kubemaster ~]# curl http://10.255.10.171:30149/info

<b> Name: Tiny Webserver Version: 0.7 Port: 7777</b><br/><br/><b> Kubernetes environment explorer usage</b><br/><br/><a href="/">/: Default landing page</a><br/><a href="/info">/info: Show version & usage</a><br/><a href="/fs">/fs: Complete file system as seen by this container.</a><br/><a href="/env">/env: Environment variables as seen by this container.</a><br/><a href="/podname">/podname: Podname or Hostname as seen by this container.</a><br/><a href="/healthz">/healthz: Just respond 200 ok for health checks</a><br/><a href="/quit">/quit: Cause this container to exit.</a><br/>



- Barracuda -


[관련 포스팅 목록]

[Kubernetes] CentOS 7.3 으로 Kubernetes Cluster 구성(with Flannel)-1/4

[Kubernetes] CentOS 7.3 으로 Kubernetes Cluster 구성(노드 추가하기)-2/4

[Kubernetes] 1.7.3/1.7.4, kubeadm 으로 L3 네트워크 기반 Cluster 구성(with Calico CNI)-3/4

[Kubernetes] Hyper-converged GlusterFs integration with Heketi -4/4




저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


Kubernetes Cluster 에서 사용할 스토리지로 외부의 Glusterfs 를 Dynamic Provisioning 스토리지로 사용할 경우에 대한 구축 방법에 대한 앞선 포스팅(http://bryan.wiki/283)에 더하여, 또 다른 Dynamic Provisioning 솔루션이 대해 정리해 보겠다. 즉, 본 시리즈(1~3)의 현재까지의 구축 환경을 그대로 활용하여, 추가로 Glusterfs를 컨테이너 방식으로 각 Minion 의 Pod(DaemonSet)에 위치 시켜 각 Minion 에 추가장착된 HDD(그림에서는 /dev/sdx 로 표시)를 glusterfs의 분산 디스크로 사용, Heketi API 를 통한 kubernetes 와의 통합된 환경을 구축하고 실제로 PV를 만들고 테스트해 보기로 한다.


이러한 Hyper-Converged 방식을 쓰게 되면 외부의 Glusterfs 클러스터 등의 별도 스토리지 솔루션이 불필요하게 되며, 필요한 만큼 스토리지를 갖춘 Minion 노드를 추가하게 되면 Kubernetes 를 위함 컴퓨팅 자원과 스토리지 공간이 함께 늘어나는, 진정한 Scalable Comupting Cluster를 갖출 수 있게 된다.


Static Provisioning 에 대해서는 이전 포스팅에서 이미 간략히 설명했으므로, 별도 언급은 하지 않겠으며, 이러한 Dynamic Provisioning 이 가능한 스토리지 방식으로는 GlusterFS  외에도 OpenStack Cinder, AWS EBS, GCE Persistent Disk, Ceph RBD, NetApp Trident 등이 있다.





* 기존 1~3 편에서 작업했던 설정 환경을 그대로 사용하여, kubenode1/2/3 을 각각 GlusterFs pod를 통해 Gluster 스토리지 노드로 사용하는 구성이다.

* 참고 자료: gluster_kubernetes.pdf



사전 준비(Prerequisites)


* 모든 k8s 노드에서 GlusterFS Client, SELinux 설정(setsebool)

[root@kubenode01 ~]# yum install -y centos-release-gluster310.noarch

[root@kubenode01 ~]# yum install -y glusterfs-client

[root@kubenode01 ~]# setsebool -P virt_sandbox_use_fusefs on



github gluster repo 다운로드


[root@kubemaster ~]# git clone https://github.com/gluster/gluster-kubernetes.git

Cloning into 'gluster-kubernetes'...

remote: Counting objects: 2024, done.

remote: Compressing objects: 100% (14/14), done.

remote: Total 2024 (delta 5), reused 9 (delta 2), pack-reused 2008

Receiving objects: 100% (2024/2024), 987.73 KiB | 456.00 KiB/s, done.

Resolving deltas: 100% (1041/1041), done.


[root@kubemaster ~]# cd gluster-kubernetes/deploy

[root@kubemaster deploy]# cp topology.json.sample topology.json

[root@kubemaster deploy]# vi topology.json


{
    "clusters": [
        {
            "nodes": [
                {
                    "node": {
                        "hostnames": {
                            "manage": [
                                "kubenode1"
                            ],
                            "storage": [
                                "10.255.10.171"
                            ]
                        },
                        "zone": 1
                    },
                    "devices": [
                        "/dev/sdb"
                    ]
                },
                {
                    "node": {
                        "hostnames": {
                            "manage": [
                                "kubenode2"
                            ],
                            "storage": [
                                "10.255.20.170"
                            ]
                        },
                        "zone": 1
                    },
                    "devices": [
                        "/dev/sdb"
                    ]
                },
                {
                    "node": {
                        "hostnames": {
                            "manage": [
                                "kubenode3"
                            ],
                            "storage": [
                                "10.255.20.171"
                            ]
                        },
                        "zone": 1
                    },
                    "devices": [
                        "/dev/sdb"
                    ]
                }
            ]
        }
    ]
}


Glusterfs, Heketi Pod와 Svc 설치 - gk-deploy 실행


[root@kubemaster deploy]# ./gk-deploy -g

Welcome to the deployment tool for GlusterFS on Kubernetes and OpenShift.


Before getting started, this script has some requirements of the execution

environment and of the container platform that you should verify.


The client machine that will run this script must have:

 * Administrative access to an existing Kubernetes or OpenShift cluster

 * Access to a python interpreter 'python'


Each of the nodes that will host GlusterFS must also have appropriate firewall

rules for the required GlusterFS ports:

 * 2222  - sshd (if running GlusterFS in a pod)

 * 24007 - GlusterFS Management

 * 24008 - GlusterFS RDMA

 * 49152 to 49251 - Each brick for every volume on the host requires its own

   port. For every new brick, one new port will be used starting at 49152. We

   recommend a default range of 49152-49251 on each host, though you can adjust

   this to fit your needs.


The following kernel modules must be loaded:

 * dm_snapshot

 * dm_mirror

 * dm_thin_pool


For systems with SELinux, the following settings need to be considered:

 * virt_sandbox_use_fusefs should be enabled on each node to allow writing to

   remote GlusterFS volumes


In addition, for an OpenShift deployment you must:

 * Have 'cluster_admin' role on the administrative account doing the deployment

 * Add the 'default' and 'router' Service Accounts to the 'privileged' SCC

 * Have a router deployed that is configured to allow apps to access services

   running in the cluster


Do you wish to proceed with deployment?


[Y]es, [N]o? [Default: Y]: 

Using Kubernetes CLI.

Using namespace "default".

Checking for pre-existing resources...

  GlusterFS pods ... not found.

  deploy-heketi pod ... not found.

  heketi pod ... not found.

Creating initial resources ... serviceaccount "heketi-service-account" created

clusterrolebinding "heketi-sa-view" created

clusterrolebinding "heketi-sa-view" labeled

OK

node "kubenode1" labeled

node "kubenode2" labeled

node "kubenode3" labeled

daemonset "glusterfs" created

Waiting for GlusterFS pods to start ... OK

secret "heketi-config-secret" created

secret "heketi-config-secret" labeled

service "deploy-heketi" created

deployment "deploy-heketi" created

Waiting for deploy-heketi pod to start ... OK

Creating cluster ... ID: d9b90e275621648ad6aacb67759662c9

Creating node kubenode1 ... ID: 858e047eb0cb8943be7b1cd947a26e3c

Adding device /dev/sdb ... OK

Creating node kubenode2 ... ID: 6e9368eea425afb561846576c7b7df70

Adding device /dev/sdb ... OK

Creating node kubenode3 ... ID: 0c3da29a5fa92681a5bc64cf32e9e7e5

Adding device /dev/sdb ... OK

heketi topology loaded.

Saving /tmp/heketi-storage.json

secret "heketi-storage-secret" created

endpoints "heketi-storage-endpoints" created

service "heketi-storage-endpoints" created

job "heketi-storage-copy-job" created

service "heketi-storage-endpoints" labeled

pod "deploy-heketi-2199298601-mrh2p" deleted

service "deploy-heketi" deleted

job "heketi-storage-copy-job" deleted

deployment "deploy-heketi" deleted

secret "heketi-storage-secret" deleted

service "heketi" created

deployment "heketi" created

Waiting for heketi pod to start ... OK


heketi is now running and accessible via http://172.31.45.204:8080 . To run

administrative commands you can install 'heketi-cli' and use it as follows:


  # heketi-cli -s http://172.31.45.204:8080 --user admin --secret '<ADMIN_KEY>' cluster list


You can find it at https://github.com/heketi/heketi/releases . Alternatively,

use it from within the heketi pod:


  # /usr/bin/kubectl -n default exec -it <HEKETI_POD> -- heketi-cli -s http://localhost:8080 --user admin --secret '<ADMIN_KEY>' cluster list


For dynamic provisioning, create a StorageClass similar to this:


---

apiVersion: storage.k8s.io/v1beta1

kind: StorageClass

metadata:

  name: glusterfs-storage

provisioner: kubernetes.io/glusterfs

parameters:

  resturl: "http://172.31.45.204:8080"



Deployment complete!



Heketi CLI 를 통한 Gluster Cluster 정상 동작 확인


[root@kubemaster deploy]# echo "export HEKETI_CLI_SERVER=$(kubectl get svc/heketi --template 'http://{{.spec.clusterIP}}:{{(index .spec.ports 0).port}}')" | tee -a ~/.bashrc

export HEKETI_CLI_SERVER=http://10.128.23.39:8080


[root@kubemaster deploy]# ~/heketi-client/bin/heketi-cli cluster list

Clusters:

d9b90e275621648ad6aacb67759662c9


[root@kubemaster deploy]# ~/heketi-client/bin/heketi-cli cluster info d9b90e275621648ad6aacb67759662c9

Cluster id: d9b90e275621648ad6aacb67759662c9

Nodes:

0c3da29a5fa92681a5bc64cf32e9e7e5

6e9368eea425afb561846576c7b7df70

858e047eb0cb8943be7b1cd947a26e3c

Volumes:

254add2981864590710f85567895758a


[root@kubemaster ~]# ~/heketi-client/bin/heketi-cli volume info 254add2981864590710f85567895758a

Name: heketidbstorage

Size: 2

Volume Id: 254add2981864590710f85567895758a

Cluster Id: d9b90e275621648ad6aacb67759662c9

Mount: 10.255.10.171:heketidbstorage

Mount Options: backup-volfile-servers=10.255.20.170,10.255.20.171

Durability Type: replicate

Distributed+Replica: 3



* Gluster Volume 생성 테스트

[root@kubemaster ~]# ~/heketi-client/bin/heketi-cli volume create --size=1

Name: vol_5a876cb91239b4de80c3a00797888254

Size: 1

Volume Id: 5a876cb91239b4de80c3a00797888254

Cluster Id: d9b90e275621648ad6aacb67759662c9

Mount: 10.255.10.171:vol_5a876cb91239b4de80c3a00797888254

Mount Options: backup-volfile-servers=10.255.20.170,10.255.20.171

Durability Type: replicate

Distributed+Replica: 3


[root@kubemaster ~]# ~/heketi-client/bin/heketi-cli volume delete 5a876cb91239b4de80c3a00797888254

Volume 5a876cb91239b4de80c3a00797888254 deleted



Kubernetes 에서 Gluster PV 사용을 위한 준비와 PV 생성 테스트

  • StorageClass 를 생성한다
  • metadata의 name 에서 지정하는 값이, 실제로 사용시 요청되는 persistent volume claim(pvc) 에서 annotation 의 값으로 사용된다
  • resturl 에는 위에서 생성한 환경 변수 HEKETI_CLI_SERVER 의 값과 동일한 URL을 등록한다

[root@kubemaster ~]# mkdir gluster-storage-setup-test

[root@kubemaster ~]# cd gluster-storage-setup-test/

[root@kubemaster gluster-storage-setup-test]# vi 00-gluster-storageclass.yaml

apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
  name: glusterfs-storage
provisioner: kubernetes.io/glusterfs
parameters:
  resturl: "http://10.128.23.39:8080"


[root@kubemaster gluster-storage-setup-test]# kubectl create -f 00-gluster-storageclass.yaml 

storageclass "glusterfs-storage" created


* 1 GB 용량의 pvc(스토리지 할당 요청)을 만들어서 테스트해 본다

[root@kubemaster gluster-storage-setup-test]# vi test-pvc-1gi.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: test-dyn-pvc
 annotations:
   volume.beta.kubernetes.io/storage-class: glusterfs-storage
spec:
 accessModes:
  - ReadWriteOnce
 resources:
   requests:
     storage: 1Gi

* accessModes는 RWO(ReadWriteOnce), ROX(ReadOnlyMany), RWX(ReadWriteMany) 의 3 종류가 있다


[root@kubemaster gluster-storage-setup-test]# kubectl create -f test-pvc-1gi.yaml 

persistentvolumeclaim "test-dyn-pvc" created


[root@kubemaster gluster-storage-setup-test]# kubectl get pvc,pv

NAME               STATUS    VOLUME                                     CAPACITY   ACCESSMODES   STORAGECLASS        AGE

pvc/test-dyn-pvc   Bound     pvc-1f3065fe-8900-11e7-827f-08002729d0c4   1Gi        RWO           glusterfs-storage   11s


NAME                                          CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM                  STORAGECLASS        REASON    AGE

pv/pvc-1f3065fe-8900-11e7-827f-08002729d0c4   1Gi        RWO           Delete          Bound     default/test-dyn-pvc   glusterfs-storage             6s

* test-dyn-pvc 에 대응하는 pv가 만들어져 있음을 볼 수 있다



생성된 PV를 사용하는 Pod 를 생성하고 마운트된 볼륨 내용을 다뤄 보자


[root@kubemaster gluster-storage-setup-test]# vi test-nginx-pvc.yaml 

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-pv
  labels:
    name: nginx-pod-pv
spec:
  containers:
  - name: nginx-pod-pv
    image: gcr.io/google_containers/nginx-slim:0.8
    ports:
    - name: web
      containerPort: 80
    volumeMounts:
    - name: gluster-vol1
      mountPath: /usr/share/nginx/html
  volumes:
  - name: gluster-vol1
    persistentVolumeClaim:
      claimName: test-dyn-pvc

[root@kubemaster gluster-storage-setup-test]# kubectl create -f test-nginx-pvc.yaml 

pod "nginx-pod-pv" created

* Pod 내의 마운트포인트는 nginx 기본 html 저장위치인 /usr/share/nginx/html

* PV 이름을 직접 사용하는 것이 아니라 pvc 명인 test-dyn-pvc을 사용한다



[root@kubemaster gluster-storage-setup-test]# kubectl get pods -o wide

NAME                         READY     STATUS    RESTARTS   AGE       IP               NODE

busybox-for-dnstest          1/1       Running   29         1d        172.31.35.72     kubenode2

glusterfs-22v6s              1/1       Running   0          9h        10.255.20.171    kubenode3

glusterfs-pjsvb              1/1       Running   0          9h        10.255.10.171    kubenode1

glusterfs-tpd0h              1/1       Running   0          9h        10.255.20.170    kubenode2

heketi-2660258935-spb5g      1/1       Running   0          9h        172.31.45.204    kubenode3

hostnames-2923313648-3104j   1/1       Running   0          1d        172.31.35.73     kubenode2

hostnames-2923313648-h4n8j   1/1       Running   0          1d        172.31.45.199    kubenode3

hostnames-2923313648-r89h4   1/1       Running   0          1d        172.31.205.197   kubenode1

nginx-pod-pv                 1/1       Running   0          3m        172.31.35.77     kubenode2


[root@kubemaster gluster-storage-setup-test]# kubectl exec -it nginx-pod-pv -- /bin/sh

# df -h

Filesystem                                                                                      Size  Used Avail Use% Mounted on

...

10.255.10.171:vol_989e89f4758c8749c00ed6fd2fc1db92                                             1016M   33M  983M   4% /usr/share/nginx/html

...

# echo 'Hello World from GlusterFS!!!' > /usr/share/nginx/html/index.html

# exit


[root@kubemaster gluster-storage-setup-test]# curl http://172.31.35.77

Hello World from GlusterFS!!!

* Web 서버의 source, index.html 을 작성(변경)

* nginx Pod 의 IP를 이용해서 URL에 대한 Web 접속 테스트



GlusterFS Pod(Gluster 노드 역할) 내부에서 스토리지 내부 데이터 직접 확인


[root@kubemaster ~]# kubectl exec -it glusterfs-22v6s -- /bin/sh

sh-4.2# mount | grep heketi

/dev/mapper/cl-root on /var/lib/heketi type xfs (rw,relatime,seclabel,attr2,inode64,noquota)

/dev/mapper/vg_be4e161a83f2835fb8e9c61ee2513a69-brick_8ff06f0a54b9155179dd70a5fa02784b on /var/lib/heketi/mounts/vg_be4e161a83f2835fb8e9c61ee2513a69/brick_8ff06f0a54b9155179dd70a5fa02784b type xfs (rw,noatime,seclabel,nouuid,attr2,inode64,logbsize=256k,sunit=512,swidth=512,noquota)

/dev/mapper/vg_be4e161a83f2835fb8e9c61ee2513a69-brick_7cebe446228298f5b11b171567edf30f on /var/lib/heketi/mounts/vg_be4e161a83f2835fb8e9c61ee2513a69/brick_7cebe446228298f5b11b171567edf30f type xfs (rw,noatime,seclabel,nouuid,attr2,inode64,logbsize=256k,sunit=512,swidth=512,noquota)

sh-4.2# mount | grep heketi

sh-4.2# cd /var/lib/heketi/mounts/vg_be4e161a83f2835fb8e9c61ee2513a69/brick_7cebe446228298f5b11b171567edf30f/brick

sh-4.2# cat index.html

Hello World from GlusterFS!!!



불필요한 테스트용 볼륨, Pod 삭제


[root@kubemaster gluster-storage-setup-test]# kubectl delete -f test-nginx-pvc.yaml 

pod "nginx-pod-pv" deleted


[root@kubemaster gluster-storage-setup-test]# kubectl delete -f test-pvc-1gi.yaml 

persistentvolumeclaim "test-dyn-pvc" deleted


[root@kubemaster gluster-storage-setup-test]# kubectl get pv

No resources found.


[root@kubemaster gluster-storage-setup-test]# ~/heketi-client/bin/heketi-cli volume list

Id:254add2981864590710f85567895758a    Cluster:d9b90e275621648ad6aacb67759662c9    Name:heketidbstorage

* kubectl 로 pvc 삭제를 수행하면 pv가 삭제되고, Heketi CLI 에서 해당 Gluster Volume 이 삭제되어 있음을 확인할 수 있다




- Barracuda -


[관련 글 목록]

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] CentOS 7.3 으로 Kubernetes Cluster 구성(with Flannel)-1/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] CentOS 7.3 으로 Kubernetes Cluster 구성(노드 추가하기)-2/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] 1.7.3/1.7.4, kubeadm 으로 L3 네트워크 기반 Cluster 구성(with Calico CNI)-3/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] Hyper-converged GlusterFs integration with Heketi -4/4

[Technical/Cloud, 가상화, PaaS] - [GlusterFS & Kubernetes] External Gluster PV with Heketi CLI/Rest API



저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들







4개의 시리즈 중 세 번 째로, 이번에는 2017/08 최종 버전인 Kubernetes 1.7.4 의 kubeadm 을 통한 클러스터 구축을 서로 다른 네트워크가 L3로 연결된 환경에서 구현하는 과정과 방법에 대해 알아 보고, 기본적 테스트와 검증을 진행하는 내용으로 마무리 하고자 한다.



각 노드에 해당하는 서버의 준비


  • Master: CentOS 7.3(1611), CPU 2, Memory 1.2GB, IP 10.255.10.170
  • Minion 1: CentOS 7.3(1611), CPU 2, Memory 1.2GB, IP 10.255.10.171
  • Minion 2: CentOS 7.3(1611), CPU 2, Memory 1.2GB, IP 10.255.20.170
  • Minion 3: CentOS 7.3(1611), CPU 2, Memory 1.2GB, IP 10.255.20.171



각 노드 기본 설정


* 전체 서버 개별 설정


# hostnamectl set-hostname kubemaster


# hostnamectl set-hostname kubenode1


# hostnamectl set-hostname kubenode2


# hostnamectl set-hostname kubenode3



* 전체 서버 공통 설정


# vi /etc/hosts

...

10.255.10.170 kubemaster

10.255.10.171 kubenode1

10.255.20.170 kubenode2

10.255.20.171 kubenode3

* 필수는 아님: kubeadm 으로 통한 node join 시 Warning 발생 . 작업 환경 내에 직접 관리하는 DNS 가 없을 경우, 편의를 위해 사용하는 방식임


# yum makecache fast

# yum install -y epel-release

# yum provides docker

# yum install -y docker-1.12.6-32.git88a4867.el7.centos.x86_64 chrony

# systemctl stop firewalld && systemctl disable firewalld

# systemctl enable docker && systemctl start docker 


# vi /etc/yum.repos.d/kubernetes.repo

[kubernetes]

name=Kubernetes

baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64

enabled=1

gpgcheck=1

repo_gpgcheck=1

gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg

        https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg



K8s Master 설정


[root@kubemaster ~]# vi /etc/sysctl.conf 

...

net.bridge.bridge-nf-call-iptables=1

net.bridge.bridge-nf-call-ip6tables=1

net.netfilter.nf_conntrack_max = 786432

[root@kubemaster ~]# sysctl -p


[root@kubemaster ~]# vi /etc/chrony.conf

...

server time.bora.net iburst

allow 10.255.0.0/16

local stratum 10

...

[root@kubemaster ~]# systemctl enable chronyd && systemctl restart chronyd

[root@kubemaster ~]# chronyc tracking

[root@kubemaster ~]# chronyc sources -v


[root@kubemaster ~]# yum update -y 

[root@kubemaster ~]# yum install -y kubelet kubeadm kubectl kubernetes-cni nmap bind-utils net-tools chrony wget fping jq git bash-completion

[root@kubemaster ~]# systemctl enable kubelet && systemctl start kubelet


[root@kubemaster ~]# source /etc/profile.d/bash_completion.sh

[root@kubemaster ~]# source <(kubectl completion bash)

* kubectl 명령 옵션의 자동 완성 기능 설정(option 사항임)


[root@kubemaster ~]# setenforce 0

* Permissive 로 임시 전환(부팅 후 enforcing 으로 자동 전환됨)


[root@kubemaster ~]# vi /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

...

Environment="KUBELET_DNS_ARGS=--cluster-dns=10.128.0.10 --cluster-domain=cluster.local"

...

[root@kubemaster ~]# systemctl daemon-reload

[root@kubemaster ~]# systemctl restart kubelet


* Cluster IP 범위(=service-cidr) 를 Default 값인 10.96.0.0/12 와 다른 값으로 바꾸려면, DNS IP도 그에 맞게 수정해 두어야 한다

* 해당 변경은 모든 Minion 노드에서도 각각 수행해 주어야 함(1.8.x 버전에서는 kubeadm init 옵션에서 이 부분을 동적으로 반영하도록 수정될 수 있음)


[root@kubemaster ~]# kubeadm init --skip-preflight-checks --pod-network-cidr 172.31.0.0/16 --service-cidr 10.128.0.0/12 --service-dns-domain "cluster.local" --apiserver-advertise-address 10.255.10.170

[kubeadm] WARNING: kubeadm is in beta, please do not use it for production clusters.

[init] Using Kubernetes version: v1.7.4

[init] Using Authorization modes: [Node RBAC]

[preflight] Skipping pre-flight checks

[kubeadm] WARNING: starting in 1.8, tokens expire after 24 hours by default (if you require a non-expiring token use --token-ttl 0)

[certificates] Generated CA certificate and key.

[certificates] Generated API server certificate and key.

[certificates] API Server serving cert is signed for DNS names [kubemaster kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.128.0.1 10.255.10.170]

[certificates] Generated API server kubelet client certificate and key.

[certificates] Generated service account token signing key and public key.

[certificates] Generated front-proxy CA certificate and key.

[certificates] Generated front-proxy client certificate and key.

[certificates] Valid certificates and keys now exist in "/etc/kubernetes/pki"

[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/admin.conf"

[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"

[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/controller-manager.conf"

[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/scheduler.conf"

[apiclient] Created API client, waiting for the control plane to become ready

[apiclient] All control plane components are healthy after 99.501670 seconds

[token] Using token: 826c89.818bf5757e8aa2cc

[apiconfig] Created RBAC rules

[addons] Applied essential addon: kube-proxy

[addons] Applied essential addon: kube-dns


Your Kubernetes master has initialized successfully!


To start using your cluster, you need to run (as a regular user):


  mkdir -p $HOME/.kube

  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

  sudo chown $(id -u):$(id -g) $HOME/.kube/config


You should now deploy a pod network to the cluster.

Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:

  http://kubernetes.io/docs/admin/addons/


You can now join any number of machines by running the following on each node

as root:


  kubeadm join --token 826c89.818bf5757e8aa2cc 10.255.10.170:6443

* 즉시 완료되지는 않으며, /var/log/message 에 connection refused .. 메시지가 다량 관찰 되며, 수 분~수 십분 가량 걸릴 수 있음

* 완료된 후에도 journal log 또는 /var/log/messages log에는 network plugin is not read: cni config uninitialized 오류는 계속 나옴(정상 상태)



K8s Minion(worker) 노드 전체 설정


[root@kubenode1 ~]# vi /etc/sysctl.conf 

...

net.bridge.bridge-nf-call-iptables=1

net.bridge.bridge-nf-call-ip6tables=1

net.netfilter.nf_conntrack_max = 786432

[root@kubenode1 ~]# sysctl -p


[root@kubenode1 ~]# vi /etc/chrony.conf

...

server 10.255.10.170 iburst

local stratum 10

...

[root@kubenode1 ~]# systemctl enable chronyd && systemctl restart chronyd

[root@kubenode1 ~]# chronyc tracking

[root@kubenode1 ~]# chronyc sources -v


[root@kubenode1 ~]#  yum update -y  

[root@kubenode1 ~]#  yum install -y kubelet kubeadm kubernetes-cni net-tools



K8s Master 노드 kubectl 실행환경 설정


[root@kubemaster ~]# mkdir -p $HOME/.kube

[root@kubemaster ~]# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

[root@kubemaster ~]# chown $(id -u):$(id -g) $HOME/.kube/config

[root@kubemaster ~]# export KUBECONFIG=$HOME/.kube/config

[root@kubemaster ~]# echo "export KUBECONFIG=$HOME/.kube/config" | tee -a ~/.bashrc



K8s Minion 노드 설정 후 각각 추가


[root@kubenode1 ~]# systemctl enable kubelet && systemctl start kubelet

[root@kubenode1 ~]# setenforce 0
[root@kubemaster ~]# vi /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

...

Environment="KUBELET_DNS_ARGS=--cluster-dns=10.128.0.10 --cluster-domain=cluster.local"

...

[root@kubemaster ~]# systemctl daemon-reload

[root@kubemaster ~]# systemctl restart kubelet


[root@kubenode1 ~]# kubeadm join --token 102952.1a7dd4d8d9f4cc5 10.255.10.170:6443

* Token 값은 새로운 노드를 추가할 떄 재사용 가능하며, 분실시에는 Master에서 'kubeadm token list' 로 알아낼 수 있다


K8s Master, Minion 노드 & Pod 실행 상태 확인


[root@kubemaster ~]# kubectl get nodes

NAME         STATUS     AGE       VERSION

kubemaster   NotReady   3m        v1.7.4

kubenode1    NotReady   9s        v1.7.4

kubenode2    NotReady   5s        v1.7.4

kubenode3    NotReady   1s        v1.7.4


[root@kubemaster ~]# kubectl get pods --all-namespaces

NAMESPACE     NAME                                 READY     STATUS    RESTARTS   AGE

kube-system   etcd-kubemaster                      1/1       Running   0          4m

kube-system   kube-apiserver-kubemaster            1/1       Running   0          3m

kube-system   kube-controller-manager-kubemaster   1/1       Running   0          4m

kube-system   kube-dns-2425271678-08gjk            0/3       Pending   0          4m

kube-system   kube-proxy-5hgsc                     1/1       Running   0          4m

kube-system   kube-proxy-68r71                     1/1       Running   0          1m

kube-system   kube-proxy-h262f                     1/1       Running   0          1m

kube-system   kube-proxy-s4n74                     1/1       Running   0          1m

kube-system   kube-scheduler-kubemaster            1/1       Running   0          4m

* kube-dns 이외의 모든 Pod 는 Running 상태이어야 하며, kube-dns 는 Network fabric solution 을 설치하기 전까지는 Pending 으로 나올 수 있음

(kubeadm은 network provider-agnostic함)



K8s Master, Calico CNI(Container Network Interface) plugin 설치 & 네트워크 활성화


[root@kubemaster ~]# git clone https://github.com/DragOnMe/kubernetes-1.7.3-with-calico-and-test-sample.git k8s-setup-test

[root@kubemaster ~]# cd k8s-setup-test/kube-cni-calico


[root@kubemaster kube-cni-calico]# vi calico.yaml 

...

17     etcd_endpoints: "http://10.128.232.136:6666"

...

113   clusterIP: 10.128.232.136

...

180             # Configure the IP Pool from which Pod IPs will be chosen.

181             - name: CALICO_IPV4POOL_CIDR

182               value: "172.31.0.0/16"

...

* calico_etcd 주소: 10.96.232.136 (default) 에서 10.128.232.136 으로 변경(Cluster IP cidr 범위 내에 있어야 함)

* CALICO_IPV4POOL_CIDR: 172.31.0.0/16(kubeadm init 시의 pod-network-cidr 값과 동일해야 함)

  CALICO_IPV4POOL_CIDR 범위 내의 IP가 Minion 노드의 tunl0 에 할당 되며, Pod에도 동적으로 할당 됨


[root@kubemaster ~]# cd ~

[root@kubemaster ~]# kubectl apply -f k8s-setup-test/kube-cni-calico/calico.yaml

configmap "calico-config" created

daemonset "calico-etcd" created

service "calico-etcd" created

daemonset "calico-node" created

deployment "calico-policy-controller" created

clusterrolebinding "calico-cni-plugin" created

clusterrole "calico-cni-plugin" created

serviceaccount "calico-cni-plugin" created

clusterrolebinding "calico-policy-controller" created

clusterrole "calico-policy-controller" created

serviceaccount "calico-policy-controller" created


[root@kubemaster ~]# kubectl get pods -n kube-system -o wide

NAME                                       READY     STATUS              RESTARTS   AGE       IP              NODE

calico-etcd-695ln                          0/1       ImagePullBackOff    0          5m        10.255.10.170   kubemaster

calico-node-278k3                          0/2       ContainerCreating   0          5m        10.255.20.171   kubenode3

calico-node-4l1f4                          0/2       ContainerCreating   0          5m        10.255.10.170   kubemaster

calico-node-cr5cr                          0/2       ErrImagePull        0          5m        10.255.20.170   kubenode2

calico-node-dr73h                          0/2       ContainerCreating   0          5m        10.255.10.171   kubenode1

calico-policy-controller-336633499-drd9n   0/1       Pending             0          5m        <none>          <none>

etcd-kubemaster                            1/1       Running             0          14m       10.255.10.170   kubemaster

kube-apiserver-kubemaster                  1/1       Running             0          13m       10.255.10.170   kubemaster

kube-controller-manager-kubemaster         1/1       Running             0          14m       10.255.10.170   kubemaster

kube-dns-2425271678-08gjk                  0/3       Pending             0          14m       <none>          <none>

kube-proxy-5hgsc                           1/1       Running             0          14m       10.255.10.170   kubemaster

kube-proxy-68r71                           1/1       Running             0          11m       10.255.10.171   kubenode1

kube-proxy-h262f                           1/1       Running             0          11m       10.255.20.170   kubenode2

kube-proxy-s4n74                           1/1       Running             0          10m       10.255.20.171   kubenode3

kube-scheduler-kubemaster                  1/1       Running             0          14m       10.255.10.170   kubemaster


[root@kubemaster ~]# kubectl get nodes

NAME         STATUS     AGE       VERSION

kubemaster   NotReady   29m       v1.7.4

kubenode1    Ready      26m       v1.7.4

kubenode2    NotReady   25m       v1.7.4

kubenode3    Ready      25m       v1.7.4

* Calico Network Pod가 정상 기동되기 전까지는 모든 노드는 NotReady 상태로 보일 수도 있다. 위의 과정을 보면 Calico Pod 가 Ready로 올라옴에 따라

노드가 하나씩 Ready 상태로 바뀌고 있는 것을 알 수 있다(Calico node Pod 의 경우 전체 Minion에 걸쳐 완료되려면 수 십분 정도 걸릴 수도 있다)


...

Aug 24 02:06:43 localhost kubelet: E0824 02:06:43.126000    3032 pod_workers.go:182] Error syncing pod 0c9bb31d-8825-11e7-b7d7-08002729d0c4 ("calico-etcd-695ln_kube-system(0c9bb31d-8825-11e7-b7d7-08002729d0c4)"), skipping: failed to "StartContainer" for "calico-etcd" with ImagePullBackOff: "Back-off pulling image \"quay.io/coreos/etcd:v3.1.10\""

Aug 24 02:06:43 localhost kubelet: W0824 02:06:43.531722    3032 cni.go:189] Unable to update cni config: No networks found in /etc/cni/net.d

Aug 24 02:06:43 localhost kubelet: E0824 02:06:43.531821    3032 kubelet.go:2136] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized

Aug 24 02:06:48 localhost kubelet: W0824 02:06:48.533281    3032 cni.go:189] Unable to update cni config: No networks found in /etc/cni/net.d

Aug 24 02:06:48 localhost kubelet: E0824 02:06:48.533882    3032 kubelet.go:2136] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized

...

* Example /var/log/messages


[root@kubemaster ~]# kubectl get pods -n kube-system -o wide

NAME                                       READY     STATUS    RESTARTS   AGE       IP              NODE

calico-etcd-695ln                          1/1       Running   0          45m       10.255.10.170   kubemaster

calico-node-278k3                          2/2       Running   7          45m       10.255.20.171   kubenode3

calico-node-4l1f4                          2/2       Running   5          45m       10.255.10.170   kubemaster

calico-node-cr5cr                          2/2       Running   8          45m       10.255.20.170   kubenode2

calico-node-dr73h                          2/2       Running   6          45m       10.255.10.171   kubenode1

calico-policy-controller-336633499-drd9n   1/1       Running   9          45m       10.255.20.171   kubenode3

etcd-kubemaster                            1/1       Running   0          30m       10.255.10.170   kubemaster

kube-apiserver-kubemaster                  1/1       Running   0          30m       10.255.10.170   kubemaster

kube-controller-manager-kubemaster         1/1       Running   0          30m       10.255.10.170   kubemaster

kube-dns-2425271678-08gjk                  3/3       Running   4          54m       172.31.45.192   kubenode3

kube-proxy-5hgsc                           1/1       Running   0          54m       10.255.10.170   kubemaster

kube-proxy-68r71                           1/1       Running   0          50m       10.255.10.171   kubenode1

kube-proxy-h262f                           1/1       Running   0          50m       10.255.20.170   kubenode2

kube-proxy-s4n74                           1/1       Running   0          50m       10.255.20.171   kubenode3

kube-scheduler-kubemaster                  1/1       Running   0          30m       10.255.10.170   kubemaster

* 시간이 지남에 따라 점차로 Pod들이 자리를 잡으며, 후반으로 가면 kube-dns가 Running 상태로 바뀌고 Pod Network의 IP를 할당 받음을 볼 수 있다


[root@kubemaster ~]# kubectl get nodes

NAME         STATUS    AGE       VERSION

kubemaster   Ready     55m       v1.7.4

kubenode1    Ready     51m       v1.7.4

kubenode2    Ready     51m       v1.7.4

kubenode3    Ready     51m       v1.7.4

* 모든 노드가 Ready 상태로 바뀌면 설치가 완료되어 Cluster 의 사용이 가능한 상태가 된 것이다



Cluster 정상 상태 및 DNS 동작 확인을 위한 Pod 생성


[root@kubemaster ~]# mkdir k8s-test && cd k8s-test

[root@kubemasterk8s-test]# vi busybox-for-dnstest.yaml

apiVersion: v1
kind: Pod
metadata:
  name: busybox-for-dnstest
  namespace: default
spec:
  containers:
  - image: busybox
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    name: busybox-container
  restartPolicy: Always


[root@kubemasterk8s-test]# vi test-hostnames-deploy.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: hostnames
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: hostnames
    spec:
      containers:
      - name: hostnames
        image: gcr.io/google_containers/serve_hostname
        ports:
        - containerPort: 9376
          protocol: TCP


[root@kubemasterk8s-test]# vi test-hostnames-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: hostnames
spec:
  selector:
    app: hostnames
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 9376


[root@kubemaster k8s-test]# kubectl create -f busybox-for-dnstest.yaml

pod "busybox-for-dnstest" created

[root@kubemaster k8s-test]# kubectl create -f test-hostnames-deploy.yaml 

deployment "hostnames" created

[root@kubemaster k8s-test]# kubectl create -f test-hostnames-svc.yaml 

service "hostnames" created



Cluster DNS 정상 작동 상태 확인


* Kubernetes 클러스터의 정상적인 서비스를 위해서 가장 중요한 요소 중 하나인 DNS 기능을 확인해 봄으로써, 전체 클러스터의 기본적 동작을 점검할 수 있다.


[root@kubemaster k8s-test]# kubectl exec -it busybox-for-dnstest -- sh

/ # nslookup yahoo.com

Server:    10.128.0.10

Address 1: 10.128.0.10 kube-dns.kube-system.svc.cluster.local


Name:      yahoo.com

Address 1: 2001:4998:58:c02::a9 ir1.fp.vip.bf1.yahoo.com

Address 2: 2001:4998:c:a06::2:4008 ir1.fp.vip.gq1.yahoo.com

Address 3: 2001:4998:44:204::a7 ir1.fp.vip.ne1.yahoo.com

Address 4: 206.190.36.45 ir1.fp.vip.gq1.yahoo.com

Address 5: 98.138.253.109 ir1.fp.vip.ne1.yahoo.com

Address 6: 98.139.180.149 ir1.fp.vip.bf1.yahoo.com

/ # nslookup kubernetes

Server:    10.128.0.10

Address 1: 10.128.0.10 kube-dns.kube-system.svc.cluster.local


Name:      kubernetes

Address 1: 10.128.0.1 kubernetes.default.svc.cluster.local

/ # nslookup hostnames

Server:    10.128.0.10

Address 1: 10.128.0.10 kube-dns.kube-system.svc.cluster.local


Name:      hostnames

Address 1: 10.128.89.48 hostnames.default.svc.cluster.local


/ # wget -O- http://hostnames

Connecting to hostnames (10.98.89.48:80)

hostnames-2923313648-rtlgg

-                    100% |*******************************|    27   0:00:00 ETA

/ # wget -O- http://hostnames

Connecting to hostnames (10.98.89.48:80)

hostnames-2923313648-f8s4x

-                    100% |*******************************|    27   0:00:00 ETA

/ # wget -O- http://hostnames

Connecting to hostnames (10.98.89.48:80)

hostnames-2923313648-rtlgg

-                    100% |*******************************|    27   0:00:00 ETA

/ # wget -O- http://hostnames

Connecting to hostnames (10.98.89.48:80)

hostnames-2923313648-j5tp0

-                    100% |*******************************|    27   0:00:00 ETA

/ # wget -O- http://hostnames

Connecting to hostnames (10.98.89.48:80)

hostnames-2923313648-rtlgg

-                    100% |*******************************|    27   0:00:00 ETA

/ # wget -O- http://hostnames

Connecting to hostnames (10.98.89.48:80)

hostnames-2923313648-f8s4x

-                    100% |*******************************|    27   0:00:00 ETA

* DNS의 Round-Robin 에 의해, wget 실행시 마다 다른 Pod에 번갈아서 접속됨을 확인할 수 있다



Cluster Network 정상 작동 확인(Master 노드 - ping - Cluster IP)


* Kubernetes 클러스터 내의 가상네트워크(ClusterIP) 의 정상 동작 여부 확인

[root@kubemaster k8s-test]# kubectl get pods -o wide

NAME                         READY     STATUS    RESTARTS   AGE       IP               NODE

busybox-for-dnstest          1/1       Running   0          4m        172.31.35.72     kubenode2

hostnames-2923313648-3104j   1/1       Running   0          4m        172.31.35.73     kubenode2

hostnames-2923313648-h4n8j   1/1       Running   0          4m        172.31.45.199    kubenode3

hostnames-2923313648-r89h4   1/1       Running   0          4m        172.31.205.197   kubenode1


[root@kubemaster k8s-test] kubectl get po -o json | jq .items[].status.podIP -r | fping

172.31.35.72 is alive

172.31.35.73 is alive

172.31.45.199 is alive

172.31.205.197 is alive

* 현재 리스트업 되는 모든 pod 들에 대해 ping 실행



Addon 설치 - Dashboard with Heapster, InfluxDB/Grafana


* Heapster, influxdb & grafana 설치

[root@kubemaster ~]# cd ~/k8s-setup-test/heapster

[root@kubemaster heapster]# kubectl create -f deploy/kube-config/influxdb

[root@kubemaster heapster]# kubectl create -f deploy/kube-config/rbac/heapster-rbac.yaml

* k8s-setup-test/heapster 의 내용은 https://github.com/kubernetes/heapster 의 내용과 동일함


* kubernetes-dashboard 설치

[root@kubemaster ~]# cd ~/k8s-setup-test/kubernetes-dashboard/

[root@kubemaster kubernetes-dashboard]# kubectl apply -f kubernetes-dashboard.yaml

* k8s-setup-test/kubernetes-dashboard/kubernetes-dashboard.yaml 내용은   https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/kubernetes-dashboard.yaml 의 내용과 동일함


* Dashboard 접속 URL 확인

[root@kubemaster ~]# kubectl -n kube-system expose deployment kubernetes-dashboard --name kubernetes-dashboard-nodeport --type=NodePort

[root@kubemaster ~]# kubectl get svc -nkube-system

NAME                            CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE

calico-etcd                     10.128.232.136    <none>        6666/TCP         20h

heapster                        10.108.175.104   <none>        80/TCP           15h

kube-dns                        10.128.0.10       <none>        53/UDP,53/TCP    21h

kubernetes-dashboard            10.103.62.100    <none>        80/TCP           1h

kubernetes-dashboard-nodeport   10.102.218.165   <nodes>       9090:30707/TCP   1h

monitoring-grafana              10.102.181.221   <none>        80/TCP           15h

monitoring-influxdb             10.99.75.72      <none>        8086/TCP         15h

* http://10.255.10.170:30707/ 로 dashboard 접속





클러스터 유지보수, 해체 등 운영 방법


  • 불가피한 사정으로 Cluster 를 셧다운 시는 Minion - Master 순으로 수행
  • 재기동 시는 Master-Minion 순으로 수행


* Minion 노드 유지보수(At master node)

[root@kubemaster ~]# kubectl get nodes

NAME         STATUS    AGE       VERSION

kubemaster   Ready     6d        v1.7.4

kubenode1    Ready     6d        v1.7.4

kubenode2    Ready     6d        v1.7.4

kubenode3    Ready     6d        v1.7.4

[root@kubemaster ~]#  kubectl drain hostname --force --ignore-daemonsets

node "hostname" cordoned

WARNING: Ignoring DaemonSet-managed pods: calico-node-f6an0, kube-proxy-jv53g

* 해당 노드는 cordoned 상태가 되며, 내부의 Pod는 유지되나, Pod의 추가 deploy 대상에서 제외됨


* 점검 후 복귀

[root@kubemaster ~]# kubectl uncordon node hostname


* 삭제 후 재투입(drain 후)

[root@kubemaster ~]# kubectl delete node hostname

[root@kubemaster ~]# kubeadm join --token xxxxxxxxxxxxxx 10.255.10.170:6443

* Minion 노드 재설치 및 신규 투입 과정과 동일함



* Cluster 해체시

[root@kubemaster ~]# kubectl drain hostname1 --delete-local-data --force --ignore-daemonsets

[root@kubemaster ~]# kubectl delete node hostname1

[root@kubemaster ~]# kubectl drain hostname2 --delete-local-data --force --ignore-daemonsets

[root@kubemaster ~]# kubectl delete node hostname2

...

[root@kubemaster ~]# kubeadm reset

[root@kubemaster ~]# systemctl daemon-reload

[root@kubemaster ~]# systemctl restart docker

[root@kubemaster ~]# systemctl restart kubelet

...

* At master node


[root@kubenode1 ~]# kubeadm reset

[root@kubenode1 ~]# systemctl daemon-reload

[root@kubenode1 ~]# systemctl restart docker

[root@kubenode1 ~]# systemctl restart kubelet

* Minion nodes



- Barracuda -


[관련 글 목록]

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] CentOS 7.3 으로 Kubernetes Cluster 구성(with Flannel)-1/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] CentOS 7.3 으로 Kubernetes Cluster 구성(노드 추가하기)-2/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] 1.7.3/1.7.4, kubeadm 으로 L3 네트워크 기반 Cluster 구성(with Calico CNI)-3/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] Hyper-converged GlusterFs integration with Heketi -4/4

[Technical/Cloud, 가상화, PaaS] - [GlusterFS & Kubernetes] External Gluster PV with Heketi CLI/Rest API



저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


전편(http://bryan.wiki/281)에 이어서, 이번에는 기존 k8s 클러스터에 새로운 워커 노드를 추가하는 방법에 대해 알아 보자. 클러스터의 환경 구성은 전편과 동일하다.



새로운(3 번) 노드의 준비


* Minion 3: CPU 1, Memory 1.2GB, IP 10.255.10.183


기존 노드와 동일/유사 스펙과 설정으로 구성해 나간다



추가 Minion 노드 시간동기화 설정


[root@kubenode3 ~]# yum -y install --enablerepo=virt7-docker-common-release kubernetes etcd flannel wget git

[root@kubenode3 ~]# yum update -y

[root@kubenode3 ~]# vi /etc/chrony.conf

...

server 10.255.10.180 iburst

local stratum 10

...

[root@kubenode3 ~]# systemctl enable chronyd

[root@kubenode3 ~]# systemctl start chronyd

[root@kubenode3 ~]# chronyc tracking

Reference ID    : 10.255.10.180 (10.255.10.180)

Stratum         : 4

Ref time (UTC)  : Sun Aug 13 10:03:29 2017

System time     : 0.000000011 seconds fast of NTP time

Last offset     : -0.000016258 seconds

RMS offset      : 0.001879302 seconds

Frequency       : 27.832 ppm fast

Residual freq   : -0.014 ppm

Skew            : 0.427 ppm

Root delay      : 0.009792 seconds

Root dispersion : 0.045111 seconds

Update interval : 64.6 seconds

Leap status     : Normal


[root@kubenode3 ~]# chronyc sources -v

210 Number of sources = 1


  .-- Source mode  '^' = server, '=' = peer, '#' = local clock.

 / .- Source state '*' = current synced, '+' = combined , '-' = not combined,

| /   '?' = unreachable, 'x' = time may be in error, '~' = time too variable.

||                                                 .- xxxx [ yyyy ] +/- zzzz

||      Reachability register (octal) -.           |  xxxx = adjusted offset,

||      Log2(Polling interval) --.      |          |  yyyy = measured offset,

||                                \     |          |  zzzz = estimated error.

||                                 |    |           \

MS Name/IP address         Stratum Poll Reach LastRx Last sample

===============================================================================

^* 10.255.10.180                 3   6   377    17    +23us[  +47us] +/-   51ms



추가 Minion 노드 kubernetes  config/kubelet/flannel 설정


기존 노드로부터 설정파일 등을 복사하여 수정/반영한다

[root@kubenode3 ~]# mkdir k8s-files

[root@kubenode3 ~]# cd k8s-files/

[root@kubenode3k8s-files]# scp root@10.255.10.181:/etc/yum.repos.d/virt7-docker-common-release.repo /etc/yum.repos.d/

[root@kubenode3 k8s-files]# scp root@10.255.10.181:/etc/kubernetes/config /etc/kubernetes/config

[root@kubenode3 k8s-files]# scp root@10.255.10.181:/etc/kubernetes/kubelet .

[root@kubenode3 k8s-files]# vi kubelet

...

KUBELET_HOSTNAME="--hostname-override=10.255.10.183"

...

[root@kubenode3 k8s-files]# cp kubelet /etc/kubernetes/kubelet


[root@kubenode3 k8s-files]# scp root@10.255.10.181:/etc/sysconfig/flanneld /etc/sysconfig/flanneld


[root@kubenode3 k8s-files]# cd ~

[root@kubemaster ~]# scp root@10.255.10.181:~/kube-*.sh .

[root@kubemaster ~]# systemctl disable firewalld

[root@kubemaster ~]# systemctl stop firewalld



Kubernetes Master controller manager 설정


[root@kubemaster ~]# vi /etc/kubernetes/controller-manager

...

KUBELET_ADDRESSES="--machines=10.255.10.181, 10.255.10.182, 10.255.10.183"

...

[root@kubemaster ~]# systemctl restart kube-controller-manager

[root@kubemaster ~]# systemctl status kube-controller-manager

● kube-controller-manager.service - Kubernetes Controller Manager

   Loaded: loaded (/usr/lib/systemd/system/kube-controller-manager.service; enabled; vendor preset: disabled)

   Active: active (running) since 일 2017-08-13 16:02:14 KST; 11s ago

     Docs: https://github.com/GoogleCloudPlatform/kubernetes

 Main PID: 6221 (kube-controller)

   Memory: 24.2M

   CGroup: /system.slice/kube-controller-manager.service

           └─6221 /usr/bin/kube-controller-manager --logtostderr=true --v=0 --master=http://10.255.10.18...


 8월 13 16:02:15 kubemaster kube-controller-manager[6221]: E0813 16:02:15.243100    6221 actual_state...st

 8월 13 16:02:15 kubemaster kube-controller-manager[6221]: I0813 16:02:15.265929    6221 replication_...v4

 8월 13 16:02:15 kubemaster kube-controller-manager[6221]: I0813 16:02:15.266201    6221 replication_...20

 8월 13 16:02:15 kubemaster kube-controller-manager[6221]: I0813 16:02:15.274222    6221 nodecontroll...e:

 8월 13 16:02:15 kubemaster kube-controller-manager[6221]: W0813 16:02:15.274255    6221 nodecontroll...p.

 8월 13 16:02:15 kubemaster kube-controller-manager[6221]: W0813 16:02:15.274290    6221 nodecontroll...p.

 8월 13 16:02:15 kubemaster kube-controller-manager[6221]: I0813 16:02:15.274310    6221 nodecontroll...l.

 8월 13 16:02:15 kubemaster kube-controller-manager[6221]: I0813 16:02:15.274456    6221 event.go:217] ...

 8월 13 16:02:15 kubemaster kube-controller-manager[6221]: I0813 16:02:15.274478    6221 event.go:217] ...

 8월 13 16:02:25 kubemaster kube-controller-manager[6221]: I0813 16:02:25.203637    6221 garbagecolle...ge

Hint: Some lines were ellipsized, use -l to show in full.



추가 Minion 노드 kubernetes 컴포넌트 시작


[root@kubenode3 ~]# for SERVICES in flanneld docker kubelet kube-proxy ; do \

>    systemctl enable $SERVICES ; \

>done

Created symlink from /etc/systemd/system/multi-user.target.wants/flanneld.service to /usr/lib/systemd/system/flanneld.service.

Created symlink from /etc/systemd/system/docker.service.requires/flanneld.service to /usr/lib/systemd/system/flanneld.service.

Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.

Created symlink from /etc/systemd/system/multi-user.target.wants/kubelet.service to /usr/lib/systemd/system/kubelet.service.

Created symlink from /etc/systemd/system/multi-user.target.wants/kube-proxy.service to /usr/lib/systemd/system/kube-proxy.service.

[root@kubenode3 ~]# ./kube-start-node.sh

--- Starting flanneld ---

Done


--- Starting docker ---

Done


--- Starting kubelet ---

Done


--- Starting kube-proxy ---

Done


[root@kubenode3 ~]# ./kube-check-node.sh 

--- docker ---

active

enabled


--- flanneld ---

active

enabled


--- kubelet ---

active

enabled


--- kube-proxy ---

active

enabled



Kubernetes Master 에서 클러스터 상태 확인


[root@kubemaster ~]# kubectl get nodes

NAME            STATUS    AGE

10.255.10.181   Ready     9d

10.255.10.182   Ready     9d

10.255.10.183   Ready     6m




- Barracuda -


[관련 글 목록]

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] CentOS 7.3 으로 Kubernetes Cluster 구성(with Flannel)-1/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] CentOS 7.3 으로 Kubernetes Cluster 구성(노드 추가하기)-2/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] 1.7.3/1.7.4, kubeadm 으로 L3 네트워크 기반 Cluster 구성(with Calico CNI)-3/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] Hyper-converged GlusterFs integration with Heketi -4/4

[Technical/Cloud, 가상화, PaaS] - [GlusterFS & Kubernetes] External Gluster PV with Heketi CLI/Rest API



저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


Kubernetes 또는 OpenShift 로 PaaS 를 구축할 경우에, 사용자 환경에 대응하는 인프라 측의 2개 부문의 솔루션을 반드시 고민해야 하는데 그중 하나가 Persistent Storage 에 대한 솔루션이다(다른 하나는 Overlay Network 을 위한 터널링 솔루션/Fabric 이 되겠다).


대개는 NFS 와 같은 일반적인 스토리지 모델을 적용하여 Persistent Volume을 일정 갯수 이상 만들어 놓고 사용/반납하는 식으로 사용(Static Provisioning)할 것 같은데, GlusterFS를 스토리지로 사용하고 각 워커노드에서 Native GlusterFS Client 를 활용한다면, Auto-healing 과 분산 파일 스토리지의 장점을 살리면서 스토리지 공간도 동적으로 사용(Dynamic Provisioning)할 수 있겠다(Ceph 의 경우도 비슷한 장점을 가지고 있기는 하다).


본 포스팅에서는 제목에서 알 수 있듯이 Kubernetes 또는 OpenShift Cluster 외부에 존재하는 Gluster 클러스터를, Kubernetes를 Persistent Volume을 위한 스토리지로 Gluster 를 활용하는 내용을 다룰 것이다. 참고로 Kubernetes 내에 Gluster+Heketi Pod 를 DaemonSet 으로 Deploy한 다음 Hyper-Converged 방식으로 사용하는 경우도 존재하는데, 다음에 한 번 따로 다루어 보기로 하겠다.



구현 목표


기존에 설치/사용중인 Kubernetes(또는 OpenShift) 클러스터가 이미 존재한다고 가정하고, Persistent Storage로 일정 대수의 Gluster 서버(여기서는 2대-뒤에 3대 이상이 필요하다는 것이 함정)을 구성한 후 Heketi RestAPI 서버를 이용, Container(또는 Pod)에서 마운트 해서 사용하는 모델을 구현해 보기로 한다. Heketi를 활용한다면, Gluster 스토리지를 Rest 방식으로 OpenStack Manilla, OpenShift, Kubernetes 와 연동/통합하는 좋은 방안 중 하나일 듯하다.



* Reference 1: https://github.com/heketi/heketi

* Reference 2: https://keithtenzer.com/2017/03/24/storage-for-containers-using-gluster-part-ii/


사전 준비


스토리지용 서버 2대와 Gluster Console/Heketi API 서버 1대를 준비한다. gluster 서버의 설치에 관해서는 따로 다루지 않는다(필요하면 기존 포스팅 중 http://bryan.wiki/270 를 참고하기를 권한다). 또한 모든 서버간의 시간동기화 설정은 꼭 해 두는 것이 좋다.


* Heketi 서버(Gluster Console/Client): CentOS 7, IP - 192.168.50.100

* Gluster-1: CentOS 7, 3 disks(/dev/sdb, /dev/sdc, /dev/sdd), IP - 192.168.50.101

* Gluster-2: CentOS 7, 3 disks(/dev/sdb, /dev/sdc, /dev/sdd), IP - 192.168.50.102

* Gluster-3: 뒤에 언급한다


2대의 Gluster 서버와 1대의 Heketi 서버에서 /etc/hosts 파일에 아래 호스트 정보를 추가해 둔다


# vi /etc/hosts

...

192.168.50.100 gfs-console

192.168.50.101 gfs-node01

192.168.50.102 gfs-node02


Gluster 서버의 환경 점검


Firewall을 사용중이라면 firewall-cmd 를 통해 포트를 개방해 둔다.


[root@gfs-node01 ~]# firewall-cmd --add-port=24007-24008/tcp --add-port=49152-49664/tcp --add-port=2222/tcp  --add-port=8080/tcp

[root@gfs-node01 ~]# firewall-cmd --runtime-to-permanent


기존에 Gluster Volume을 사용중인 서버들이었다면 모든 볼륨을 삭제하고, Gluster 용으로 사용중인 모든 디스크를 unmount해야 하며 /etc/fstab 도 그에 맞게 수정한 후(reboot), 각 디스크의 파티션을 삭제하고 Raw Device 상태로 만들어 둔다. lsblk 를 수행했을 때 디바이스(/dev/sdb 등) 아래에 파티션(sdb1 등)이 보인다면 반드시 삭제한다.


[root@gfs-node01 ~]# fdisk /dev/sdb

p

(... 파티션이 있다면 ...)

d

w


[root@gfs-node01 ~]# fdisk /dev/sdc

p

d

w

...

...

[root@gfs-node01 ~]# wipefs -a /dev/sdb

[root@gfs-node01 ~]# wipefs -a /dev/sdc

[root@gfs-node01 ~]# wipefs -a /dev/sdd



Heketi 서버 설정


* Heketi 서버 데몬과 클라이언트 모듈을 설치한다

[root@gfs-console ~]# yum install -y epel-release

[root@gfs-console ~]# yum install -y centos-release-gluster310.noarch

[root@gfs-console ~]# yum install -y glusterfs-client heketi heketi-client

[root@gfs-console ~]# yum update -y


* Gluster 노드 1, 2에 Passwordless ssh 접속을 가능하도록 키를 교환한다

[root@gfs-console ~]# ssh-keygen -f /etc/heketi/heketi_key -t rsa -N ''

[root@gfs-console ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@gfs-node01

[root@gfs-console ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@gfs-node02

[root@gfs-console ~]# chown heketi:heketi /etc/heketi/heketi_key*


* Heketi 동작 방식 및 키 파일 위치 설정

[root@gfs-console ~]# vi /etc/heketi/heketi.json

...

31     "executor": "ssh",

32 

33     "_sshexec_comment": "SSH username and private key file information",

34     "sshexec": {

35       "keyfile": "/etc/heketi/heketi_key",

36       "user": "root",

37       "port": "22",

38       "fstab": "/etc/fstab"

39     },

...

[root@gfs-console ~]# systemctl enable heketi

[root@gfs-console ~]# systemctl restart heketi

[root@gfs-console ~]# curl http://localhost:8080/hello

Hello from Heketi


*Gluster 서버의 Cluster 설정 정보와 디스크 Brick 구성을 정의한다

[root@gfs-console ~]# vi heketi_topology.json

{
  "clusters": [
    {
      "nodes": [
        {
          "node": {
            "hostnames": {
              "manage": [
                "192.168.50.101"
              ],
              "storage": [
                "192.168.50.101"
              ]
            },
            "zone": 1
          },
          "devices": [
            "/dev/sdb",
            "/dev/sdc",
            "/dev/sdd"
          ]
        },
        {
          "node": {
            "hostnames": {
              "manage": [
                "192.168.50.102"
              ],
              "storage": [
                "192.168.50.102"
              ]
            },
            "zone": 1
          },
          "devices": [
            "/dev/sdb",
            "/dev/sdc",
            "/dev/sdd"
          ]
        }
      ]
    }
  ]
}

[root@gfs-console ~]# export HEKETI_CLI_SERVER=http://192.168.50.100:8080

[root@gfs-console ~]# echo "export HEKETI_CLI_SERVER=http://192.168.50.100:8080" | tee -a ~/.bashrc


[root@gfs-console ~]# heketi-cli topology load --json=heketi_topology.json

Creating cluster ... ID: fae7eb318f26475afbd552236670b979

    Creating node 192.168.50.101 ... ID: 325db3146610c1546c32159ce0ca5b2c 

        Adding device /dev/sdb ... OK 

        Adding device /dev/sdc ... OK         

        Adding device /dev/sdd ... OK 

    Creating node 192.168.50.102 ... ID: 8d59a92dc64963e58c650be192762784 

        Adding device /dev/sdb ... OK 

        Adding device /dev/sdc ... OK 

        Adding device /dev/sdd ... OK

[root@gfs-console ~]# heketi-cli volume create --size=1 --replica=2

[root@gluster-console ~]# heketi-cli volume delete 207ee2a905675bc7506cadb6cce4624f

Volume 207ee2a905675bc7506cadb6cce4624f deleted

* Gluster 노드 구성이 2대일 경우, replica=3(디폴트)로 볼륨을 생성하면 "No Space" 에러가 발생하게 됨에 유의



Kubernetes 노드(Minion) 설정


모든 k8s 노드에 GlusterFS Client 를 설치한다


[root@kubenode01 ~]# yum install -y centos-release-gluster310.noarch

[root@kubenode01 ~]# yum install -y glusterfs-client

[root@kubenode01 ~]# setsebool -P virt_sandbox_use_fusefs on

* SELinux 기본 설정에서는 Pod 등에서 remote Gluster 서버로의 write 가 불가능하게 되어 있다. 반드시 setsebool 로 fusefs 쓰기 가능 설정



[3-노드 구성의 필요성]


여기서, 현재 고려중인 2-노드 Gluster Server 구성으로는 Kubernetes 에서 Gluster Volume 생성이 불가능함이 확인되었다. 즉 Heketi-cli 로는 --replicas=2 로 명시적으로 replica 를 2로 지정할 수 있지만, Kubernetes 의 리소스 정의(yaml 파일)을 통한 PVC(Persistent Volume Claim) 방식에서는 replica 를 명시적으로 지정할 수 있는 옵션이 존재하지 않는다. 따라서 다음과 같은, 새로운 Gluster Server(node03)를 추가하는 과정이 별도로 필요하다.


* 3번째 Gluster Server 노드 셋업 및 노드 추가(1~6)


1, 2번 노드와 동일한 스펙과 디스크 구성의 새로운 서버 생성

 - Gluster-3: CentOS 7, 3 disks(/dev/sdb, /dev/sdc, /dev/sdd), IP - 192.168.50.103


2. Gluster Server 패키지 설치

# hostnamectl set-hostname gfs-node03

# yum install -y centos-release-gluster310.noarch #-> 2017/08 기준 Long Term Stable 버전

# yum install -y glusterfs-server ntp

# yum update -y

# systemctl enable glusterd

# systemctl start glusterd

# vi /etc/ntp.conf

....

server 192.168.x.y iburst # 다른 Gluster Server 노드와 동일하게 설정

...

# systemctl enable ntpd

# systemctl start ntpd

# ntpq -pn

    remote           refid      st t when poll reach   delay   offset  jitter

==============================================================================

*192.168.x.y    203.248.240.140  3 u    2   64    1    0.324    7.201   0.323


3. 각 Gluster 노드의 /etc/hosts 파일 수정

# vi /etc/hosts

...

192.168.50.100 gfs-console

192.168.50.101 gfs-node01

192.168.50.102 gfs-node02

192.168.50.103 gfs-node03


4. 새로운 노드(gfs-node03)의 방화벽 제거, 디스크 구성 등 최종 확인

[root@gfs-node03 ~]# systemctl stop firewalld

[root@gfs-node03 ~]# systemctl disable firewalld

Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.

Removed symlink /etc/systemd/system/basic.target.wants/firewalld.service.


[root@gfs-node03 ~]# systemctl status glusterd

● glusterd.service - GlusterFS, a clustered file-system server

   Loaded: loaded (/usr/lib/systemd/system/glusterd.service; enabled; vendor preset: disabled)

   Active: active (running) since Fri 2017-08-11 08:25:36 EDT; 22min ago

 Main PID: 6953 (glusterd)

   CGroup: /system.slice/glusterd.service

           └─6953 /usr/sbin/glusterd -p /var/run/glusterd.pid --log-level INFO


Aug 11 08:25:36 gfs-node03 systemd[1]: Starting GlusterFS, a clustered file-system server...

Aug 11 08:25:36 gfs-node03 systemd[1]: Started GlusterFS, a clustered file-system server.


[root@gfs-node03 ~]# lsblk

NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT

fd0           2:0    1    4K  0 disk 

sda           8:0    0   20G  0 disk 

├─sda1        8:1    0    1G  0 part /boot

└─sda2        8:2    0   19G  0 part 

  ├─cl-root 253:0    0   17G  0 lvm  /

  └─cl-swap 253:1    0    2G  0 lvm  [SWAP]

sdb           8:16   0   20G  0 disk 

sdc           8:32   0   20G  0 disk 

sdd           8:48   0   20G  0 disk 

sr0          11:0    1 1024M  0 rom

* 각 디스크는 포맷되거나 파티셔닝 되지 않은 Raw 상태 그대로 유지


5. 기존 Gluster Server 노드 중 아무 서버로 로그인, 새로운 peer 추가

[root@gfs-node02 ~]# gluster peer status

Number of Peers: 1


Hostname: gfs-node01

Uuid: 995a7229-fcde-4eeb-b699-4f5fd2829b20

State: Peer in Cluster (Connected)


[root@gfs-node02 ~]# gluster peer probe 192.168.50.103

peer probe: success.

* Gluster 클러스터 외부(Kubernetes 클러스터)에서 Gluster 클러스터로 접근하는 경우이므로, 되도록이면 IP로 probe 한다


6. Heketi 서버에서 새로운 노드 추가

[root@gluster-console ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@gfs-node03 #-> 키 교환 필수


[root@gluster-console ~]# heketi-cli node list

Id:d0517df692a2ebf40c47c73925ad21c4 Cluster:33b4b4ce590ca914e56aa606159e82c6

Id:f5a68d69da2c9f9d14243392ead4367c Cluster:33b4b4ce590ca914e56aa606159e82c6


[root@gluster-console ~]# heketi-cli node add \

> --zone=1 \

> --cluster=33b4b4ce590ca914e56aa606159e82c6 \

> --management-host-name=192.168.50.103 \

> --storage-host-name=192.168.50.103

Node information:

Id: 98c20ae34697f261ca6f45c05ca0105c

State: online

Cluster Id: 33b4b4ce590ca914e56aa606159e82c6

Zone: 1

Management Hostname 192.168.50.103

Storage Hostname 192.168.50.103

[root@gluster-console ~]# heketi-cli device add --name=/dev/sdb --node=98c20ae34697f261ca6f45c05ca0105c

Device added successfully

[root@gluster-console ~]# heketi-cli device add --name=/dev/sdc --node=98c20ae34697f261ca6f45c05ca0105c

Device added successfully

[root@gluster-console ~]# heketi-cli device add --name=/dev/sdd --node=98c20ae34697f261ca6f45c05ca0105c

Device added successfully

[root@gluster-console ~]# heketi-cli topology info

...클러스터 전체 노드/디스크 등 구성 정보...

* 기존 위에서 작업했던 heketi_topology.json 의 마지막에 새로운 노드 정보를 추가하고 heketi-cli topology load ... 를 해도 되고, 위와 같이 cli 명령을 통해 대화식으로 처리해도 된다



Kubernetes/OpenShift 에서 Heketi-Gluster 볼륨 사용을 위한 Secret, StorageClass 생성


[root@kubemaster ~]# mkdir kube-storage-gluster-heketi && cd kube-storage-gluster-heketi

[root@kubemaster kube-storage-gluster-heketi]# vi 00-gluster-secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: heketi-secret
  namespace: default
type: "kubernetes.io/glusterfs"
data:
  # echo -n "PASSWORD" | base64
  # key: PASSWORD_BASE64_ENCODED
  key: "UEFTU1dPUkQ="

[root@kubemaster kube-storage-gluster-heketi]# kubectl create -f 00-gluster-secret.yaml

secret "heketi-secret" created


[root@kubemaster kube-storage-gluster-heketi]# vi 01-gluster-heketi-storage-class.yaml 

apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
  name: gluster-heketi-external
provisioner: kubernetes.io/glusterfs
parameters:
  resturl: "http://192.168.50.100:8080"
  restuser: "admin"
  secretName: "heketi-secret"
  secretNamespace: "default"
  gidMin: "40000"
  gidMax: "50000"

[root@kubemaster kube-storage-gluster-heketi]# kubectl create -f 01-gluster-heketi-storage-class.yaml 

storageclass "gluster-heketi-external" created


[root@kubemaster kube-storage-gluster-heketi]# kubectl get secret,storageclass

NAME                            TYPE                                  DATA      AGE

secrets/default-token-10xkd     kubernetes.io/service-account-token   3         57d

secrets/heketi-secret           kubernetes.io/glusterfs               1         17h

secrets/heketi-storage-secret   Opaque                                1         1h


NAME                      TYPE

gluster-heketi-external   kubernetes.io/glusterfs

* gidMin~gidMax 값은 Storage Class 내에서 각 볼륨에 대해 유일한 값으로 순차적으로 정해지는 값이며, 볼륨이 삭제되면 gid Pool에 반납되도록 되어 있는 supplemental Group ID 값이다(PV가 attach된 Pod 내의 root 계정으로 전달되어 PV에 write 가능하게 된다. id 명령으로 확인 가능 - 참고), 별도 지정하지 않으면 2000~2147483647 사이의 값이 임의로 할당되는데, Heketi 3.x 이후 버전에서는 이 범위를 Storage Class 정의 시 지정하게 되어 있다 - 참고.


Kubernetes/OpenShift 에서 Heketi endpoint, job, secret 생성


* Heketi 서버에서 다음과 같이 실행하면 heketi-storage.json 파일이 생성된다(Gluster 서버쪽에는 heketidbstorage 볼륨이 생성)

[root@gluster-console ~]# heketi-cli setup-openshift-heketi-storage

Saving heketi-storage.json


* Kubernetes/OpenShift Master 쪽으로 Heketi 서버에서 생성한 heketi-storage.json 파일을 복사하여 kubectl create -f 한다

[root@kubemaster kube-storage-gluster-heketi]# scp root@192.168.50.100:~/heketi-storage.json ./02-heketi-storage.json

root@192.168.50.100's password: 

heketi-storage.json                                                                       100%   83KB  83.1KB/s   00:00    


[root@kubemaster kube-storage-gluster-heketi]# kubectl create -f 02-heketi-storage.json 

secret "heketi-storage-secret" created

endpoints "heketi-storage-endpoints" created

service "heketi-storage-endpoints" created

job "heketi-storage-copy-job" created


[root@kubemaster kube-storage-gluster-heketi]# kubectl get ep,job,pod,svc

NAME                                   ENDPOINTS                                            AGE

ep/heketi-storage-endpoints            192.168.50.101:1,192.168.50.102:1,192.168.50.103:1   1h

ep/kubernetes                          192.168.60.160:6443                                  58d


NAME                           DESIRED   SUCCESSFUL   AGE

jobs/heketi-storage-copy-job   1         1            1h


NAME                     READY     STATUS    RESTARTS   AGE

po/busybox-for-dnstest   1/1       Running   58         2d


NAME                                    CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE

svc/heketi-storage-endpoints            10.199.188.129   <none>        1/TCP     1h

svc/kubernetes                          10.199.0.1       <none>        443/TCP   58d

heketi-storage-copy-job이 완료(SUCCESSFUL=1) 되면 Kubernetes 클러스터 내에서 Heketi 를 통한 동적 볼륨 사용이 가능하게 된 것이다. 


Kubernetes/OpenShift 에서 PV/PVC 생성 & 확인 


Yaml 파일을 통해서 Gluster Volume으로 된 pvc, pv 생성 확인


[root@kubemaster kube-storage-gluster-heketi]# vi ex1-gluster-pvc-example.yaml 

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: gluster-dyn-pvc
 annotations:
   volume.beta.kubernetes.io/storage-class: gluster-heketi-external
spec:
 accessModes:
  - ReadWriteOnce
 resources:
   requests:
     storage: 1Gi


[root@kubemaster kube-storage-gluster-heketi]# kubectl create -f ex1-gluster-pvc-example.yaml 

persistentvolumeclaim "gluster-dyn-pvc" created


[root@kubemaster kube-storage-gluster-heketi]# kubectl get pvc

NAME              STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE

gluster-dyn-pvc   Bound     pvc-801691d7-7f04-11e7-9f70-005056a79d0a   1Gi        RWO           31m


[root@kubemaster kube-storage-gluster-heketi]# kubectl get pv

NAME                                       CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM                     REASON    AGE

pvc-801691d7-7f04-11e7-9f70-005056a79d0a   1Gi        RWO           Delete          Bound     default/gluster-dyn-pvc             27m

* Persistent Volume Claim 이 처리 되면, 그에 대응하는 Persistent Volume 이 할당된다



생성된 PV를 사용하는 Pod를 생성하고, Volume 이 정상 작동하는지 확인


[root@kubemaster kube-storage-gluster-heketi]# vi ex2-gluster-pod-pv-example.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pv-pod
  labels:
    name: nginx-pv-pod
spec:
  containers:
  - name: nginx-pv-pod
    image: gcr.io/google_containers/nginx-slim:0.8
    ports:
    - name: web
      containerPort: 80
    volumeMounts:
    - name: gluster-vol1
      mountPath: /usr/share/nginx/html
  volumes:
  - name: gluster-vol1
    persistentVolumeClaim:
      claimName: gluster-dyn-pvc


[root@kubemaster kube-storage-gluster-heketi]# kubectl create -f ex2-gluster-pod-pv-example.yaml 

pod "nginx-pv-pod" created 


[root@kubemaster kube-storage-gluster-heketi]# kubectl get pod -o wide

NAME                     READY     STATUS    RESTARTS   AGE       IP            NODE

po/busybox-for-dnstest   1/1       Running   116        4d        172.30.59.4   192.168.60.162

po/nginx-pv-pod          1/1       Running   0          2h        172.30.59.5   192.168.60.162


[root@kubemaster kube-storage-gluster-heketi]# kubectl describe pod nginx-pv-pod 

Name: nginx-pv-pod

Namespace: default

Node: 192.168.60.162/192.168.60.162

Start Time: Mon, 14 Aug 2017 05:52:21 -0400

Labels: name=nginx-pv-pod

Status: Running

IP: 172.30.59.5

Controllers: <none>

Containers:

  nginx-pv-pod:

    Container ID: docker://29e40373a1999612ed7ac50d18e2a933aa2adbb7a31d403a7dc239d4b8fbaa6d

    Image: gcr.io/google_containers/nginx-slim:0.8

    Image ID: docker-pullable://gcr.io/google_containers/nginx-slim@sha256:8b4501fe0fe221df663c22e16539f399e89594552f400408303c42f3dd8d0e52

    Port: 80/TCP

    State: Running

      Started: Mon, 14 Aug 2017 05:53:23 -0400

    Ready: True

    Restart Count: 0

    Volume Mounts:

      /usr/share/nginx/html from gluster-vol1 (rw)

      /var/run/secrets/kubernetes.io/serviceaccount from default-token-10xkd (ro)

    Environment Variables: <none>

Conditions:

  Type Status

  Initialized True 

  Ready True 

  PodScheduled True 

Volumes:

  gluster-vol1:

    Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)

    ClaimName: gluster-dyn-pvc

    ReadOnly: false

  default-token-10xkd:

    Type: Secret (a volume populated by a Secret)

    SecretName: default-token-10xkd

QoS Class: BestEffort

Tolerations: <none>

No events.


[root@kubemaster kube-storage-gluster-heketi]# kubectl exec -it nginx-pv-pod -- bash

  root@nginx-pv-pod:/# cd /usr/share/nginx/html

  root@nginx-pv-pod:/# echo 'Hello World from GlusterFS!' > index.html

  root@nginx-pv-pod:/# exit


 [root@kubemaster kube-storage-gluster-heketi]# curl http://172.30.59.5

 Hello World from GlusterFS!



Kuberbetes 노드에서 mount 상태를, GlusterFS 서버에서 볼륨 내용을 확인해 보자


[root@kubenode02 ~]# mount -l

...

192.168.50.101:vol_f8a6a9f5ef82d805c25e5cd98a63e89d on /var/lib/kubelet/pods/43e560c9-80d6-11e7-9f70-005056a79d0a/volumes/kubernetes.io~glusterfs/pvc-801691d7-7f04-11e7-9f70-005056a79d0a type fuse.glusterfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other,max_read=131072)

...


[root@gfs-node01 ~]# gluster volume info vol_f8a6a9f5ef82d805c25e5cd98a63e89d

 

Volume Name: vol_f8a6a9f5ef82d805c25e5cd98a63e89d

Type: Replicate

Volume ID: 46c88aae-2521-4d0e-b3e4-a4c2afef850b

Status: Started

Snapshot Count: 0

Number of Bricks: 1 x 3 = 3

Transport-type: tcp

Bricks:

Brick1: 192.168.50.103:/var/lib/heketi/mounts/vg_a040256530d028869b78f30a614efe3b/brick_0bd04d87fcb584d14a5b605ecad0eb9d/brick

Brick2: 192.168.50.102:/var/lib/heketi/mounts/vg_3a0181f175a826880a02d03d16e8e908/brick_c9ee2b0d42e30788005ec3b73228563e/brick

Brick3: 192.168.50.101:/var/lib/heketi/mounts/vg_3a7109f386e745ce9f990657e9388a19/brick_84f16f03a4aaa388d6ceacf607b24443/brick

Options Reconfigured:

transport.address-family: inet

performance.readdir-ahead: on

nfs.disable: on


[root@gfs-node01 ~]# ls -l /var/lib/heketi/mounts/vg_3a7109f386e745ce9f990657e9388a19/brick_84f16f03a4aaa388d6ceacf607b24443/brick

total 4

-rw-r--r--. 2 root 40000 30 Aug 14 05:59 index.html



점검용으로 만들어 본 임시 pod, pv 삭제하고 클러스터 자원 정리


[root@gkubemaster kube-storage-gluster-heketi]# kubectl delete -f ex2-gluster-pod-pv-example.yaml 

pod "nginx-pv-pod" deleted

[root@gkubemaster kube-storage-gluster-heketi]# kubectl delete -f ex1-gluster-pvc-example.yaml 

persistentvolumeclaim "gluster-dyn-pvc" deleted



- Barracuda -


[관련 글 목록]

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] CentOS 7.3 으로 Kubernetes Cluster 구성(with Flannel)-1/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] CentOS 7.3 으로 Kubernetes Cluster 구성(노드 추가하기)-2/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] 1.7.3/1.7.4, kubeadm 으로 L3 네트워크 기반 Cluster 구성(with Calico CNI)-3/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] Hyper-converged GlusterFs integration with Heketi -4/4

[Technical/Cloud, 가상화, PaaS] - [GlusterFS & Kubernetes] External Gluster PV with Heketi CLI/Rest API



저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


Kubernetes 를 CentOS 7 에서 설치하는 방법을 정리해 둔다. 3개의 VM을 준비하고, Mater 하나와 두 개의 Minion(Slave 또는 worker node, 그냥 노드라고도 한다) 구성의 Custer를 구현하되, 네트워크 백엔드 솔루션(Network Fabric)은 CoreOS 에서 기본 채택하고 있는 Flannel을 사용하여 서로 다른 호스트(Worker node, Minion)의 컨테이너간 터널링을 통한 연결이 가능하게 설정할 것이다(총 4개의 시리즈로, 마지막 포스팅에서는 Kubeadm 을 통해, BGP 라우팅이 가능한 Calico Network를 적용한 Kubernetes 클러스터를 구현해볼 예정이다)


Docker Engine과 함께 Kubernetes 를 설치하는 방법은, 모든 필수 패키지를 수작업으로 개별 설치하거나, Playbook 레시피를 적용하여 Ansible을 통해 자동화된 설치를 진행할 수도 있고 또는 Kubeadm 을 통해 간편하게 설치 및 설정을 할 수도 있다. 여기서는 첫 번 째에 언급한 수작업 설치(v1.5.2 기준, v1.6.x 이전 버전)을 진행해 보고자 한다.




준비 사항

VirtualBox VM 3개를 준비(예를 들어 본 Blog의 다른 포스트 - http://bryan.wiki/282 - 의 방법을 통해 CentOS 7.3 VM 3개를 찍어내 두도록 한다, 또는 VM 3개를 각각 만들고 일일이 ConeOS 7.3을 설치하는 수고로움을 즐겨도 좋다).


* Master: CPU 1, Memory 2GB, IP 10.255.10.180

* Minion 1: CPU 1, Memory 1.2GB, IP 10.255.10.181

* Minion 2: CPU 1, Memory 1.2GB, IP 10.255.10.182



[주의] 클러스터 구성에서 각 서버간의 시간동기화가 중요한 이슈가 될 수 있으므로, 기본적으로 ntp 또는 chrony 를 설정하고 Master 노드 또는 해당 환경 내의 전용 Time Server를 기준으로 설정해야 한다. 본 내용에서는 시간동기화 설정에 따로 다루지 않을 것이므로, 이전 포스팅 중 http://bryan.wiki/273 의 내용 중 일부를 찾아서 참고하기를 권한다.



클러스터 구성


아래의 그림과 유사한 아키텍처의 물리적 구성을 해도 좋고, VirtualBox를 통한 가상화 구성을 해도 좋다. 기본적으로 1개의 물리적 네트워크(인터넷 등 외부와 연결되는 External network)와 2개의 가상 네트워크를 사용하여 Kubernetes(=k8s) 클러스터를 구성하게 된다.





* External Private Network: 10.255.10.0/24 - Master/Minion 서버에서 사용하는 기본 네트워크

* Backend Network(Fabric): 172.31.0.0/16 - Container(Pod)가 사용하는 가상 네트워크(Flannel 을 통한 터널링)

* Service Cluster Network: 10.100.0.0/16 - Cluster 내부 DNS에 의해 IP를 자동 할당/관리하게되는 서비스용 가상 네트워크

* K8s에 의해 최초 생성되는 서비스는 'kubernetes' 이며, 항상 Service Cluster Network의 첫번째 IP(10.100.0.1)가 할당됨

* Cluster 내부 DNS(kube-dns 서비스)에는 Service Cluster Network의 IP 중 임의의 하나를 결정하고 할당(10.100.0.100)



각 노드에서 yum repository 설정, Docker/K8s/etcd/flannel 을 설치



# vi /etc/yum.repos.d/virt7-docker-common-release.repo

[virt7-docker-common-release]

name=virt7-docker-common-release

baseurl=http://cbs.centos.org/repos/virt7-docker-common-release/x86_64/os/

gpgcheck=0


# yum -y install --enablerepo=virt7-docker-common-release kubernetes etcd flannel wget git

* Kubernetes 1.5.2 버전(2016/12월 releae)이 설치된다. 아래에 소개 되는 설치/운영 방식은 이후 버전에서는 더 이상 적용되지 않는다.

* [참고] 1.6.0(2017/3월 release) 이후 버전부터는 kubeadm 을 통한 Cluster 설치/설정 운영 방식으로 동작하며, Minion 노드의 kube-proxy는 각 노드에 고루 존재하는 DaemonSet 방식의 Pod로 편재되는 아키텍처로 바뀌었으며 TLS 를 통한 암호화 통신을 기본으로 하는 등 높은 완성도와 안정화를 이루었다.



각 노드에서 Kunernetes 기본 설정


# vi /etc/kubernetes/config

# Comma separated list of nodes running etcd cluster

KUBE_ETCD_SERVERS="--etcd-servers=http://10.255.10.180:2379"


# Logging will be stored in system journal

KUBE_LOGTOSTDERR="--logtostderr=true"


# Journal message level, 0 is debug

KUBE_LOG_LEVEL="--v=0"


# Should this cluster be allowed to run privileged docker containers

KUBE_ALLOW_PRIV="--allow-privileged=false"


# Api-server endpoint used in scheduler and controller-manager

KUBE_MASTER="--master=http://10.255.10.180:8080"



Master 노드에서 etcd, API Server, Controller Manager 설정


[root@kubemaster ~]# vi /etc/etcd/etcd.conf

#[member]

ETCD_NAME=default

ETCD_DATA_DIR="/var/lib/etcd/default.etcd"

ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"

#[cluster]

ETCD_ADVERTISE_CLIENT_URLS="http://0.0.0.0:2379"


API Server 를 설정하기 위해서는 API 요청시 인증을 처리하기 위한 작업을 진행해야 한다. 웹 브라우저에서 github.com 의 공식 repository를 통해 make-ca-cert.sh 를 열어서 전체 내용을 복사, 작업 디렉토리(~/cacert)에 make-ca-cert.sh 로 저장 후, 30번쨰 라인을 다음과 같이 수정한 후, 지정된 Master와 k8s service 도메인에 대해 인증서를 생성한다.


[root@kubemaster ~]# vi make-ca-cert.sh

...

# /etc/group 에 kube 그룹이 존재함을 확인해 볼 것

cert_group=${CERT_GROUP:-kube}

...

[root@kubemaster ~]# chmod a+x make-ca-cert.sh


[root@kubemaster ~]# bash make-ca-cert.sh "10.255.10.180" "IP:10.255.10.180,IP:10.100.0.1,DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.cluster.local"


이제 API Server 를 설정한다.


[root@kubemaster ~]# vi /etc/kubernetes/apiserver

# Bind kube API server to this IP

KUBE_API_ADDRESS="--address=0.0.0.0"


# Port that kube api server listens to.

KUBE_API_PORT="--port=8080"


# Port kubelet listen on

#KUBELET_PORT="--kubelet-port=10250"


# Comma separated list of nodes in the etcd cluster

KUBE_ETCD_SERVERS="--etcd-servers=http://10.255.10.180:2379"


# Address range to use for services(Work unit of Kubernetes)

KUBE_SERVICE_ADDRESSES="--service-cluster-ip-range=10.100.0.0/16"


# default admission control policies

KUBE_ADMISSION_CONTROL="--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota"


# Add your own!

KUBE_API_ARGS="--client-ca-file=/srv/kubernetes/ca.crt --tls-cert-file=/srv/kubernetes/server.cert --tls-private-key-file=/srv/kubernetes/server.key"


Controller Manager 를 설정한다.


[root@kubemaster ~]# vi /etc/kubernetes/controller-manager

# Comma separated list of minions

KUBELET_ADDRESSES="--machines=10.255.10.181, 10.255.10.182"


# Add your own!

KUBE_CONTROLLER_MANAGER_ARGS="--root-ca-file=/srv/kubernetes/ca.crt --service-account-private-key-file=/srv/kubernetes/server.key"



각 Minion 노드에서 kubelet을 설정


[root@kubenode1 ~]# vi /etc/kubernetes/kubelet 

###

# kubernetes kubelet (minion) config


# The address for the info server to serve on (set to 0.0.0.0 or "" for all interfaces)

KUBELET_ADDRESS="--address=0.0.0.0"


# The port for the info server to serve on

#KUBELET_PORT="--port=10250"


# You may leave this blank to use the actual hostname

KUBELET_HOSTNAME="--hostname-override=10.255.10.181"


# location of the api-server

KUBELET_API_SERVER="--api-servers=http://10.255.10.180:8080"


# pod infrastructure container

#KUBELET_POD_INFRA_CONTAINER="--pod-infra-container-image=registry.access.redhat.com/rhel7/pod-infrastructure:latest"


# Add your own!

KUBELET_ARGS="--cluster-dns=10.100.0.100 --cluster-domain=cluster.local"


[root@kubenode2 ~]# vi /etc/kubernetes/kubelet 

###

# kubernetes kubelet (minion) config


# The address for the info server to serve on (set to 0.0.0.0 or "" for all interfaces)

KUBELET_ADDRESS="--address=0.0.0.0"


# The port for the info server to serve on

#KUBELET_PORT="--port=10250"


# You may leave this blank to use the actual hostname

KUBELET_HOSTNAME="--hostname-override=10.255.10.182"


# location of the api-server

KUBELET_API_SERVER="--api-servers=http://10.255.10.180:8080"


# pod infrastructure container

#KUBELET_POD_INFRA_CONTAINER="--pod-infra-container-image=registry.access.redhat.com/rhel7/pod-infrastructure:latest"


# Add your own!

KUBELET_ARGS="--cluster-dns=10.100.0.100 --cluster-domain=cluster.local"



컨테이너(Pod) 간 백엔드 네트워크를 위한 Flannel 설정


Master 노드에서 etcd 를 시작한다


[root@kubemaster ~]# systemctl enable etcd

[root@kubemaster ~]# systemctl start etcd



etcd 내에 flannel 을 위한 네트워크 정보 저장 공간을 위한 Key를 생성하고, 네트워크 설정 값을 등록한다. 다음과 같이 등록하면, /16 네트워크에 대한 /24 크기의 Flannel 서브넷이 각 Minion에 자동 할당된다


[root@kubemaster ~]# etcdctl mkdir /kube-centos/network

[root@kubemaster ~]# etcdctl mk /kube-centos/network/config "{ \"Network\": \"172.31.0.0/16\", \"SubnetLen\": 24, \"Backend\": { \"Type\": \"vxlan\" } }"



이제 모든 노드에서 다음과 같이 Flannel 에 대한 설정을 진행한다


# vi /etc/sysconfig/flanneld

# etcd URL location.  Point this to the server where etcd runs

FLANNEL_ETCD_ENDPOINTS="http://10.255.10.180:2379"


# etcd config key.  This is the configuration key that flannel queries

# For address range assignment

FLANNEL_ETCD_PREFIX="/kube-centos/network"


# Any additional options that you want to pass

#FLANNEL_OPTIONS=""



Master와 Minion 에서 firewall을 설정하고 각각 서비스를 enable한 후, 기동

(최초 설치를 시작할 때, 노드에 firewalld 패키지를 설치하지 않았다면 아래 방화벽 설정 부분은 무시해도 되며, 여기서는 Master/etcd node 에 대해서만 방화벽을 설정해 보도록 한다. Port 허용 정보는 -> 참고)


[root@kubemaster ~]# systemctl enable firewalld

[root@kubemaster ~]# systemctl start firewalld

[root@kubemaster ~]# firewall-cmd --permanent --zone=public --add-port=443/tcp

[root@kubemaster ~]# firewall-cmd --permanent --zone=public --add-port=6443/tcp

[root@kubemaster ~]# firewall-cmd --permanent --zone=public --add-port=8080/tcp

[root@kubemaster ~]# firewall-cmd --permanent --zone=public --add-port=8285/udp

[root@kubemaster ~]# firewall-cmd --permanent --zone=public --add-port=8472/udp

[root@kubemaster ~]# firewall-cmd --permanent --zone=public --add-port=2379-2380/tcp

[root@kubemaster ~]# firewall-cmd --reload


[root@kubemaster ~]# systemctl enable flanneld

[root@kubemaster ~]# systemctl start flanneld

[root@kubemaster ~]# systemctl enable kube-controller-manager

[root@kubemaster ~]# systemctl start kube-controller-manager

[root@kubemaster ~]# systemctl enable kube-scheduler

[root@kubemaster ~]# systemctl start kube-scheduler

[root@kubemaster ~]# systemctl enable kube-apiserver

[root@kubemaster ~]# systemctl start kube-apiserver


[root@kubenode1 ~]# systemctl disable firewalld

[root@kubenode1 ~]# systemctl stop firewalld


[root@kubenode1 ~]# systemctl enable flanneld

[root@kubenode1 ~]# systemctl start flanneld

[root@kubenode1 ~]# systemctl enable docker

[root@kubenode1 ~]# systemctl start docker

[root@kubenode1 ~]# systemctl enable kubelet

[root@kubenode1 ~]# systemctl start kubelet

[root@kubenode1 ~]# systemctl enable kube-proxy

[root@kubenode1 ~]# systemctl start kube-proxy



각 노드에서 /24 크기의 Flannel 서브넷의 첫 번째 IP가 docker0에 할당되어 있는지 확인해 본다


[root@kubenode02 ~]# ifconfig

docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450

        inet 172.31.83.1  netmask 255.255.255.0  broadcast 0.0.0.0

        inet6 fe80::42:80ff:fe4a:e1d6  prefixlen 64  scopeid 0x20<link>

        ether 02:42:80:4a:e1:d6  txqueuelen 0  (Ethernet)

        RX packets 236  bytes 20760 (20.2 KiB)

        RX errors 0  dropped 0  overruns 0  frame 0

        TX packets 115  bytes 8656 (8.4 KiB)

        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

...

flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450

        inet 172.31.83.0  netmask 255.255.255.255  broadcast 0.0.0.0

        ether d2:bd:7a:e9:cd:e4  txqueuelen 0  (Ethernet)

        RX packets 108  bytes 7310 (7.1 KiB)

        RX errors 0  dropped 0  overruns 0  frame 0

        TX packets 80  bytes 10160 (9.9 KiB)

        TX errors 0  dropped 1 overruns 0  carrier 0  collisions 0



현재까지의 설정이 끝나면, Master 노드에서는 kube-apiserver, etcd, kube-controller-manager, kube-scheduler, flanneld 가 정상 가동 되고 있어야 하며,

Minion 노드에서는 kube-proxy, kubelet, flanneld, docker 가 정상 가동 되고 있어야 한다(systemctl status ~ 로 확인 또는 마지막 부분의 kube-check~ 스크립트를 작성하고 실행).



클러스터의 현재 상태를 확인해 보자


[root@kubemaster ~]# kubectl get nodes

NAME            STATUS    AGE

10.255.10.181   Ready     3m

10.255.10.182   Ready     3m

[root@kubemaster ~]# kubectl cluster-info

Kubernetes master is running at http://localhost:8080


To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.



k8s Addon 의 설치와 설정


kube-dns 서비스(Cluster 내부 DNS) 의 설정으로 서비스 discovery를 가능하게 해 보자. Master 노드에서 다음과 같이 kube-dns ReplicationController와 Service를 기동한다


# mkdir k8s-addon && cd k8s-addon

# git clone https://github.com/DragOnMe/kubernetes-mod-skydns-dashboard-mon.git

# cd kubernetes-mod-skydns-dashboard-mon

# kubectl create -f DNS/skydns-rc.yaml

# vi DNS/skydns-svc.yaml

...

   clusterIP: 10.100.0.100

...

# kubectl create -f DNS/skydns-svc.yaml


각 Minion 노드의 kubelet 서비스를 재시작해 준다


# systemctl restart kubelet


kube-system 네임스페이스에 kube-dns 서비스가 작동중인지 확인해 보자


[root@kubemaster ~]# kubectl get svc --all-namespaces

NAMESPACE     NAME         CLUSTER-IP     EXTERNAL-IP   PORT(S)         AGE

default       kubernetes   10.100.0.1     <none>        443/TCP         44m

kube-system   kube-dns     10.100.0.100   <none>        53/UDP,53/TCP   46s



필수적인 것은 아니지만, k8s 클러스터의 상태 모니터링 등을 위해 도움이 되는 dashboard Addon을 설치해 보자


[root@kubemaster kubernetes-mod-skydns-dashboard-mon]# kubectl create -f Dashboard/dashboard-controller.yaml

deployment "kubernetes-dashboard" created

[root@kubemaster kubernetes-mod-skydns-dashboard-mon]# kubectl create -f Dashboard/dashboard-service.yaml

service "kubernetes-dashboard" created


dashboard 서비스의 접속은 http://10.255.10.180:8080/ui 로 접속해 보면 된다.




Heapster와 Grafana, InfluxDB를 이용한 dashboard 상의 monitoring Addon(Arun Dhyani 님 제공) 을 설치해 보자


[root@kubemaster kubernetes-mod-skydns-dashboard-mon]# kubectl create -f cluster-monitoring/influxdb

service "monitoring-grafana" created

deployment "heapster-v1.2.0" created

service "heapster" created

replicationcontroller "monitoring-influxdb-grafana-v4" created

service "monitoring-influxdb" created


Google의 gcr.io 로부터 heapster 구동에 필요한 컨테이너의 Pull & Deploy 가 진행중이다


Heapster와 influxdb, grafana 의 설치가 완료되면 대쉬보드에서 그래프와 차트가 나타남을 확인할 수 있다



부팅/종료 및 데몬 서비스 기동 순서 & Sample Script


클러스터 운영/관리 단계에서 Master 노드와 Worker 노드를 종료, 기동하는 순서와, 각 데몬 서비스를 종료하고 시작하는 순서가 중요하다. 반드시 다음의 순서를 지켜 주어야 한다


* Kubernetes 클러스터 부팅시: Master 를 먼저 시작하고 데몬 서비스 로딩이 완료되면 Worker 노드를 차례로 시작하고 데몬 서비스 로딩 확인

  Kubernetes 클러스터 종료시: Worker 노드를 먼저 종료하고 마지막에 Master 노드를 종료


* 노드 내의 데몬 서비스 기동 순서(종료는 기동 순서의 역순)

  - Master 노드: etcd > flanneld > kube-controller-manager > kube-scheduler > kube-apiserver

  - Worker 노드: flanneld > docker > kubelet > kube-proxy



* Master 노드 관리 Script

[root@kubemaster ~]# cat kube-check-master.sh 

#!/bin/bash
for SERVICES in etcd flanneld kube-apiserver kube-controller-manager kube-scheduler;
    do echo --- $SERVICES --- ;
    systemctl is-active $SERVICES ;
    systemctl is-enabled $SERVICES ;
    echo "";  
done


[root@kubemaster ~]# cat kube-start-master.sh 

#!/bin/bash
for SERVICES in etcd flanneld kube-controller-manager kube-scheduler kube-apiserver ;
    do echo --- Starting $SERVICES --- ;
    systemctl start $SERVICES ;
    echo "Done";  
    echo "";  
done


[root@kubemaster ~]# cat kube-stop-master.sh 

#!/bin/bash
for SERVICES in kube-apiserver kube-scheduler kube-controller-manager flanneld etcd ;
    do echo --- Stopping $SERVICES --- ;
    systemctl stop $SERVICES ;
    echo "Done";  
    echo "";  
done


* Worker 노드 관리 Script

[root@kubenode01 ~]# cat kube-check-node.sh 

#!/bin/bash
for SERVICES in docker flanneld kubelet kube-proxy;
    do echo --- $SERVICES --- ;
    systemctl is-active $SERVICES ;
    systemctl is-enabled $SERVICES ;
    echo "";  
done


[root@kubenode01 ~]# cat kube-start-node.sh 

#!/bin/bash
for SERVICES in flanneld docker kubelet kube-proxy ;
    do echo --- Starting $SERVICES --- ;
    systemctl start $SERVICES ;
    echo "Done";  
    echo "";  
done


[root@kubenode01 ~]# cat kube-stop-node.sh 

#!/bin/bash
for SERVICES in kube-proxy kubelet docker flanneld;
    do echo --- Stopping $SERVICES --- ;
    systemctl stop $SERVICES ;
    echo "Done";  
    echo "";  
done



- Barracuda -


[관련 글 목록]

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] CentOS 7.3 으로 Kubernetes Cluster 구성(with Flannel)-1/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] CentOS 7.3 으로 Kubernetes Cluster 구성(노드 추가하기)-2/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] 1.7.3/1.7.4, kubeadm 으로 L3 네트워크 기반 Cluster 구성(with Calico CNI)-3/4

[Technical/Cloud, 가상화, PaaS] - [Kubernetes] Hyper-converged GlusterFs integration with Heketi -4/4

[Technical/Cloud, 가상화, PaaS] - [GlusterFS & Kubernetes] External Gluster PV with Heketi CLI/Rest API



저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들