'전체'에 해당되는 글 234건


최근 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 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


특정 작업을 수행하기 위해 VirtualBox 내에 동일한 OS로 비슷한 스펙을 가진 VM을 여러 개 만들어야 할 일이 있을 것이다. 매번 OS를 설치하고 기본 패키지를 다운로드, 설치/설정을 반복하는 일이 번거롭고 시간을 뻇기는 부담으로 느껴진다면 이 방법을 한 번 써 봐도 좋겠다는 생각에서 정리해 둔다.


사전 준비


* VirtualBox가 설치된 호스트 머신

* template에 설치할 OS 이미지(ISO) 파일 준비, 여기서는 CentOS 7.3(1611) Minimal ISO


VirtualBox VM template 제작


1. 적당한 스펙의 VM(CPU1, Memory 1GB)을 새로 만들기

가상하드디스크는 VMDK 또는 VDI 형식을 선택


2. 저장소의 CDROM에 CentOS 7.3 ISO 파일 연결


3. VM 선택 후 시작(전원 켜기)


4. CentOS 7.3 설치


5. Network Manager 제거

  # systemctl stop NetworkManager

  # systemctl disable NetworkManager


6. 네트워크 설정 & reboot, 인터넷 연결 정상여부 확인

  # vi /etc/sysconfig/network-script/ifcfg-eth0

BOOTPROTO=static

TYPE=Ethernet

DEVICE=eth0

IPADDR=a.b.c.d

NETMASK=255.255.255.0

GATEWAY=a.b.c.x

DNS1=a.b.c.y

DNS2=a.b.c.z

ONBOOT=yes


7. 기본 업데이트 및 패키지(epel repo 사용을 전제로 함) 설치 & reboot

 # yum update -y

 # yum install -y epel-release https://www.rdoproject.org/repos/rdo-release.rpm

 # yum install -y net-tools bind-utils nc bzip2 yum-utils gcc kernel-devel dkms wget chrony

(gcc, kernel-devel, dkms는 linux용 VirtualBox Guest Addon을 빌드하기 위한 필수 패키지)


8. VirtualBox Menu > Insert Guest Addon CD image

 # mount /dev/cdrom /mnt

 # sh /mnt/VBoxLinuxAdditions.run

(reboot)


9. VM sealing(template 화 하기위한 봉인 작업. VMware에서의 template 작업과 비슷하지만 약간 다름)

 # vi seal.sh

#!/bin/bash 

#stop logging services 

/sbin/service rsyslog stop 

/sbin/service auditd stop 

#remove old kernels 

/bin/package-cleanup -y --oldkernels --count=1 

#clean yum cache 

/usr/bin/yum clean all 

#force logrotate to shrink logspace and remove old logs as well as truncate logs 

/usr/sbin/logrotate -f /etc/logrotate.conf 

/bin/rm -f /var/log/*-???????? /var/log/*.gz 

/bin/rm -f /var/log/dmesg.old 

/bin/rm -rf /var/log/anaconda 

/bin/cat /dev/null > /var/log/audit/audit.log 

/bin/cat /dev/null > /var/log/wtmp 

/bin/cat /dev/null > /var/log/lastlog 

/bin/cat /dev/null > /var/log/grubby 

#remove udev hardware rules 

/bin/rm -f /etc/udev/rules.d/70* 

#remove SSH host keys 

/bin/rm -f /etc/ssh/*key* 

#remove root users shell history 

/bin/rm -f ~root/.bash_history 

unset HISTFILE 

#remove root users SSH history 

/bin/rm -rf ~root/.ssh/

 # chmod +x seal.sh

 # vi /etc/sysconfig/network-scripts/ifcfg-eth0

BOOTPROTO=dhcp

TYPE=Ethernet

DEVICE=eth0

#IPADDR=a.b.c.d

#NETMASK=255.255.255.0

#GATEWAY=a.b.c.x

#DNS1=a.b.c.y

#DNS2=a.b.c.z

ONBOOT=yes

 # ./seal.sh

 # rm -f seal.sh

 # history -c

 # sys-unconfig


10. 만들어진 VM 이미지 파일(예를 들어 cent73-template.vdi 파일)을 ~/VirtualBox VMs/Cent73 template 디렉토리에서 /MyOSTemplates/ 등의 별도 디렉토리를 만들어서 복사해 두고, 해당 VM은 VirtualBox 콘솔에서 삭제한다.


VirtualBox VM template 을 활용한 VM 찍어내기


1. 원하는 스펙의 VM을 새로 만들기(VritualBox 콘솔)


하드디스크 선택 단계에서 '기존 가상 하드 디스크 파일 사용' 선택 직후, 반드시 아래 단계를 정확히 진행


1-1. 윈도우 탐색기, MacOS Finder 등을 실행하여, 앞서 저장해 둔 template 파일을 현재 만들어지고 있는 VM의 이미지 저장 디렉토리로 복사해 온다. 즉,

 * Source 파일: /MyOSTemplates/cent73-template.vdi

 * Destination 디렉토리: ~/VirtualBox VMs/Test VM from template - 1


1-2. 터미널 또는 커맨드 창에서 아래의 명령을 수행하여, 복사해 온 VM Image에 대한 UUID를 교체

 # VBoxManage internalcommands sethduuid ~/VirtualBox\ VMs/Test VM from template - 1/cent73-template.vdi



2. VirtualBox 콘솔로 다시 넘어가서


폴더 아이콘을 클릭하여 복사해 온 이미지파일(cent73-template.vdi) 선택, 만들기 클릭


3. VM 생성 전 단계에서 네트워크는 네트워크>어댑터 탭>고급>무작위모드>가상머신에 허용(또는 모두 허용)으로 설정


4. 모든 설정이 완료 되었으면 '시작'(전원 켜기) & VM 사용


- Barracuda -


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

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 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


- 전편에 이어서 -


이번에는 아래 그림에서와 같이 L3 network로 연결된 Docker 호스트간 L2 통신 방법(2)에 대해 다룬다. 네트워크가 다른 2개의 호스트 사이에 인터넷이 있든, 여러 개의 라우터가 존재하든, 호스트간의 연결이 가능하다면 이 방법을 쓸 수 있다. 예를 들어 아마존 AWS의 가상머신과 사무실의 인터넷이 연결된 Docker 호스트 내부의 Docker 사이의 연결도 물론 가능할 것이다.




사전 준비

  • 라우터로 연결된 서로 다른 네트워크를 가지는 Docker Host(VM) 2대
  • Docker Host #1: CentOS 7.3 Minimal Server(1611 ver), 10.255.20.10/24
  • Docker Host #2: CentOS 7.3 Minimal Server(1611 ver), 10.255.30.10/24



Docker & Open vSwitch 설치와 firewall 설정


* Docker 호스트에 EPEL 리포지터리와 RDO 프로젝트를 통해서 Docker와 Open vSwitch를 설치

(Open vSwich의 별도 설치 방법에 대해서는 http://bryan.wiki/276 참고)

yum clean all
yum install -y epel-release https://www.rdoproject.org/repos/rdo-release.rpm
yum install -y firewalld docker openvswitch bridge-utils
yum update -y
systemctl start openvswitch firewalld
systemctl enable openvswitch firewalld


* VxLAN 터널링을 위한 패킷은 UDP 4789, 8472 포트를 통해 전송된다. firewall 설정에서 이 2개 포트를 모두 개방

firewall-cmd --add-port=4789/udp --add-port=8472/udp
firewall-cmd --permanent --add-port=4789/udp --add-port=8472/udp


* trusted 영역에 대해 10.*.*.* 대역의 통신이 모두 가능하도록 개방(docker0 네트워크 10.x.0.0/16 을 서브넷으로 포함하는 상위 네트워크의 예: 10.0.0.0/8)

* 다른 예: docker0 네트워크를 172.17.0.0/16 이나 172.18.0.0/16 ... 등으로 할 경우에는 상위 네트워크를 172.16.0.0/12 로 하면 된다

firewall-cmd --permanent --zone=trusted --add-source=172.16.0.0/12


각각의 도커 호스트에서 Default Docker 네트워크 설정


2개의 Docker 호스트의 Docker 네트워크를 기본으로 설정해 둔다. 본 과정에서는 큰 의미가 없으며, 별도 옵션을 주지 않고 컨테이너를 만들면 여기서 지정된 네트워크를 Docker 엔진이 자동으로 설정해 주게 될 것이다(본 포스팅과는 직접 연관 없는 부분으로, 이 과정은 생략 가능).

[root@dockerhost-1 ~#] /etc/sysconfig/docker-network

DOCKER_NETWORK_OPTIONS="--bip=172.17.0.1/16 --iptables=false --ip-masq=false --ip-forward=true"

[root@docker01 ~#] systemctl restart docker



[root@dockerhost-2 ~#] /etc/sysconfig/docker-network

DOCKER_NETWORK_OPTIONS="--bip=172.17.0.2/16 --iptables=false --ip-masq=false --ip-forward=true"

[root@docker02 ~#] systemctl restart docker



접속 경로의 연결을 위한 Open vSwitch Bridge와 VxLAN 설정


* dockerhost-1, dockerhost-2 에서 다음의 bash 스크립트를 작성해 두자(앞서 #1회의 내용을 스크립트 방식으로 바꾸어 보았다). 스크립트 내부의 커멘트를 참고하고, 붉은 표시된 부분을 잘 구분해서 사용해야 한다.

* 양쪽 호스트의 연결 방식은 네트워크 정보가 바뀐 것 외에는 전 편 #1에서의 방식과 거의 유사하다.


[root@dockerhost-1 ~]# vi set-vxlan-all.sh 

#!/bin/bash

#

# Making VxLAN L2 tunnel over L3 network

#

# 1. Lower part - OVS bridge & VxLAN tunnel to remote

#

if [ $# != 2 ]; then

  echo "Check params..."

  echo "Usage: $0 ovs_sw_ip/cidr target_host_ip"

  exit 1

fi

ovs-vsctl add-br ovs_sw0

ip addr add $1 dev ovs_sw0 && ip link set dev ovs_sw0 up

ovs-vsctl add-port ovs_sw0 vtep0 -- set interface vtep0 type=vxlan options:remote_ip=$2

#

# 2. VETH pair between OVS bridge and Linux bridge(docker0)

#

ip link add veth_sw0 type veth peer name veth_d0

ovs-vsctl add-port ovs_sw0 veth_sw0

brctl addif docker0 veth_d0

ip link set dev veth_sw0 up

ip link set dev veth_d0 up

#

echo "Done."

[root@dockerhost-1 ~]# chmod a+x set-vxlan-all.sh 

[root@dockerhost-1 ~]# ./set-vxlan-all.sh 172.31.0.1/12 10.255.30.10


[root@dockerhost-2 ~]# vi set-vxlan-all.sh 

내용은 dockerhost-1 와 같음

[root@dockerhost-2 ~]# chmod a+x set-vxlan-all.sh 

[root@dockerhost-2 ~]# ./set-vxlan-all.sh 172.31.0.2/12 10.255.20.10



여기까지 설정하였다면 그림에서의 하단(Open vSwitch 브리지간)의 터널 개통(VxLAN L2 터널)이 정상적으로 되었는지 확인해 보기로 하자

[root@dockerhost-1 ~]# ping 172.31.0.2

PING 172.31.0.2 (172.31.0.2) 56(84) bytes of data.

64 bytes from 172.31.0.2: icmp_seq=1 ttl=64 time=2.15 ms

64 bytes from 172.31.0.2: icmp_seq=2 ttl=64 time=3.47 ms

64 bytes from 172.31.0.2: icmp_seq=3 ttl=64 time=2.17 ms

^C

--- 172.31.0.2 ping statistics ---

3 packets transmitted, 3 received, 0% packet loss, time 2019ms

rtt min/avg/max/mdev = 2.157/2.599/3.471/0.618 ms

[root@dockerhost-1 ~]#


[root@dockerhost-2 ~]# ping 172.31.0.1

PING 172.31.0.1 (172.31.0.1) 56(84) bytes of data.

64 bytes from 172.31.0.1: icmp_seq=1 ttl=64 time=1.95 ms

64 bytes from 172.31.0.1: icmp_seq=2 ttl=64 time=6.44 ms

64 bytes from 172.31.0.1: icmp_seq=3 ttl=64 time=1.80 ms

^C

--- 172.31.0.1 ping statistics ---

3 packets transmitted, 3 received, 0% packet loss, time 2027ms

rtt min/avg/max/mdev = 1.809/3.400/6.440/2.150 ms

[root@dockerhost-2 ~]# 



컨테이너와 Linux Bridge 간의 Veth Pair를 통한 연결 


하단의 터널이 뚫렸으므로 상단의 연결선을 만들어 보자. 전 편 #1 에서와 다른 점은, 2개의 Docker 컨테이너가 동일한 네트워크(여기서의 예를 들면 172.17.0.0/16)를 가져야 하므로, 컨테이너의 IP를 직접 지정해 주어야 한다는 것이다.


하지만 아쉽게도 Docker 환경에서 기본적으로는 컨테이너의 IP를 직접 지정할 수 있는 방법은 User Defined Network(docker network create ... & docker run --net=myown --ip=... 형식) 외에는 따로 없으므로, 다음에 제공되는 스크립트를 이용해서 컨테이너의 내부 인터페이스(eth0)를 docker0 브리지와 직접 연결해 주어야 한다(Docker network namespace 영역을 직접 다룸). 처리 내용은 아래의 스크립트 내용에 담겨 있으니 자세히 보고 이해해 두자. 각 Docker 호스트에서 동일하게 set-docker-network.sh 라는 이름으로 스크립트 파일을 만들고 chmod a+x 로 실행 가능하게 설정해 둔다.


#!/bin/bash
# $1: Docker ID
# $2: Docker0 IP/CIDR
# $3: Docker0 Gateway
#
# Checking params
#
if [ $# != 3 ]; then
  echo "Check params..."
  echo "Usage: $0 docker-id-or-name docker-ip/cidr gateway"
  exit 1
fi
echo "Seting docker $1's network to... " $2", GW:" $3

#
# get pid of the docker
#
pid=$(sudo docker inspect -f '{{.State.Pid}}' $1)
# echo $pid

#
# Prepare docker network namespace
#
mkdir -p /var/run/netns
ln -s /proc/$pid/ns/net /var/run/netns/$pid
echo "Network namespace created"

#
# VETH pair create, link to docker0 linux bridge side
#
ip link add veth_doc$pid type veth peer name veth_con$pid
brctl addif docker0 veth_doc$pid
ip link set veth_doc$pid up
echo "Created veth pair and linked veth_doc$pid to docker0"

#
# Link VETH pair end-point to container's eth0
#
ip link set veth_con$pid netns $pid
ip netns exec $pid ip link set dev veth_con$pid name eth0
ip netns exec $pid ip link set eth0 up
echo "Linked veth_con$pid to container's eth0"

#
# Set network information of container's eth0
#
ip netns exec $pid ip addr add $2 dev eth0
ip netns exec $pid ip route add default via $3
echo "Set container's eth0 network ... Done"

 

dockerhost-1 호스트에서 새로운 Docker 컨테이너를 --net=none 으로 즉, 네트워크 없이 생성한다. 

[root@dockerhost-1 ~]# docker run -i -t --name=docker1 --net=none busybox /bin/sh

/ # ctrl+p, ctrl+q 로 빠져나온다


[root@dockerhost-1 ~]# ./set-docker-network.sh docker1 172.17.0.10/16 172.17.0.1

[root@dockerhost-1 ~]# docker attach docker1

/ # ifconfig

eth0      Link encap:Ethernet  HWaddr EA:88:7E:B1:E5:08  

          inet addr:172.17.0.10  Bcast:0.0.0.0  Mask:255.255.0.0

          inet6 addr: fe80::e888:7eff:feb1:e508/64 Scope:Link

          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

          RX packets:71 errors:0 dropped:0 overruns:0 frame:0

          TX packets:47 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:1000 

          RX bytes:5743 (5.6 KiB)  TX bytes:4016 (3.9 KiB)


lo        Link encap:Local Loopback  

          inet addr:127.0.0.1  Mask:255.0.0.0

          inet6 addr: ::1/128 Scope:Host

          UP LOOPBACK RUNNING  MTU:65536  Metric:1

          RX packets:0 errors:0 dropped:0 overruns:0 frame:0

          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:1 

          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


/ # 

* docker1 부분은 이름 없이 생성한 경우는  docker ps 로 보이는 컨테이너 ID를 사용해도 된다



dockerhost-2 호스트에서 새로운 Docker 컨테이너를 같은 방식으로 생성하되 IP 부분만, 같은 네트워크의 다른 IP 주소로 바꾼다. 

[root@dockerhost-2 ~]# docker run -i -t --name=docker2 --net=none busybox /bin/sh

/ # ctrl+p, ctrl+q 로 빠져나온다


[root@dockerhost-2 ~]# ./set-docker-network.sh docker2 172.17.0.11/16 172.17.0.2

[root@dockerhost-2 ~]# docker attach docker2

/ # ifconfig

eth0      Link encap:Ethernet  HWaddr 2E:69:95:56:52:FA  

          inet addr:172.17.0.11  Bcast:0.0.0.0  Mask:255.255.0.0

          inet6 addr: fe80::2c69:95ff:fe56:52fa/64 Scope:Link

          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

          RX packets:74 errors:0 dropped:0 overruns:0 frame:0

          TX packets:70 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:1000 

          RX bytes:6206 (6.0 KiB)  TX bytes:5262 (5.1 KiB)


lo        Link encap:Local Loopback  

          inet addr:127.0.0.1  Mask:255.0.0.0

          inet6 addr: ::1/128 Scope:Host

          UP LOOPBACK RUNNING  MTU:65536  Metric:1

          RX packets:0 errors:0 dropped:0 overruns:0 frame:0

          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:1 

          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


/ # ping 172.17.0.10

PING 172.17.0.10 (172.17.0.10): 56 data bytes

64 bytes from 172.17.0.10: seq=0 ttl=64 time=2.427 ms

64 bytes from 172.17.0.10: seq=1 ttl=64 time=2.061 ms

64 bytes from 172.17.0.10: seq=2 ttl=64 time=1.767 ms

64 bytes from 172.17.0.10: seq=3 ttl=64 time=2.203 ms

^C

--- 172.17.0.10 ping statistics ---

4 packets transmitted, 4 packets received, 0% packet loss

round-trip min/avg/max = 1.767/2.114/2.427 ms

/ #

* docker2 컨테이너에서 docker1 컨테이너로 ping 연결이 가능한지 확인



컨테이너간 연결 최종 확인


마지막으로, 실제로 2개의 컨테이너간의 연결이 확실한지를 nc(NetCat)을 통해서 테스트해 보자.


dockerhost-1 호스트의 컨테이너에서 라우팅 상태 확인 후, 80 포트로 nc 리스닝

/ # route -n

Kernel IP routing table

Destination     Gateway         Genmask         Flags Metric Ref    Use Iface

0.0.0.0         172.17.0.1      0.0.0.0         UG    0      0        0 eth0

172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

/ # nc -l -p 8080 0.0.0.0

Test from docker2 container...

/ # 


dockerhost-2 호스트의 컨테이너에서 라우팅 상태 확인 후, docker1 컨테이너로 nc 접속(메시지 전송)

/ # route -n

Kernel IP routing table

Destination     Gateway         Genmask         Flags Metric Ref    Use Iface

0.0.0.0         172.17.0.2      0.0.0.0         UG    0      0        0 eth0

172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

/ # echo "Test from docker2 container..." | nc 172.17.0.10 80

/ #




Linux 브리지, OVS 브리지 & VxLAN 설정 초기화를 위한 스크립트


Bonus: 생성된 브리지와 인터페이스 등의 설정 정보를 초기화하는 스크립트가 필요해 보인다. 생성된 순서를 되짚어 가며 삭제 등의 처리를 일일이 해야 하므로, 테스트 과정을 수정, 반복하는 경우의 귀찮은 일을 해결해 보려는 것으로 필수 과정은 아니니 참고로 보아 두면 좋겠다(스크립트 파일명은, 위의 set-vxlan-all.sh 의 설정 정보를 삭제해 주는 의미이므로 del-vxlan-all.sh 정도로 하면 되겠다).


#!/bin/bash
if [ $# != 1 ]; then
  echo "Check params..."
  echo "Usage: $0 ovs_sw_ip/cidr"
  exit 1
fi
# Turn off each port of veth pair
ip link set dev veth_d0 down
ip link set dev veth_sw0 down
# Detach port veth_d0 on docker0 linux-bridge
brctl delif docker0 veth_d0
# Detach port veth_sw0 on ovs_sw0 OVS-bridge, then remove it
ovs-vsctl del-port ovs_sw0 veth_sw0
ip link del veth_sw0
# vtep0, ovs_sw0
ovs-vsctl del-port ovs_sw0 vtep0
ip addr del $1 dev ovs_sw0
ip link set dev ovs_sw0 down
ovs-vsctl del-br ovs_sw0

* 17번째 라인의 $1 파라미터 부분은 해당 Docker 호스트에서 설정한 ovs_sw0 브리지에 할당된 IP/CIDR(여기서는 172.31.0.1/12 또는 172.31.0.2/12) 부분을 지정한다



- Barracuda -



[관련 글]


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

Barracuda

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


제목이 다소 길지만 앞으로 2회에 걸쳐서 정리할 내용은 Open vSwitch의 VxLAN 터널링을 이용하여 L2 network로 연결된 Docker 호스트의 Docker 컨테이너간 L3 통신 방법(1)과, L3 network로 연결된 Docker 호스트간 L2 통신 방법(2)에 대해 다루고자 한다. 본 1편에서 다루고자 하는 내용을 그림으로 나타내면 다음 그림과 같다. 도커/컨테이너에 대한 기본적인 개념 설명은 여기서는 다루지 않으며, 이후에 도커의 네트워크에 대해 간략히 따로 다룰 예정이다.




그림을 보면, 2개의 호스트가 Flat한 L2 Network로 연결되어 있고(SDN: Software-Defined-Network 개념으로 보면 Underlay) 각각의 호스트 내에 Docker Container 들은 서로 다른 네트워크를 가지며, VxLAN 터널을 통한 L3 연결(SDN 개념으로 보면 Overlay)가 가능한 구조임을 알 수 있다. 그림과 같은 네트워크를 설계할 때에 특히 주의해야 할 점은, 하단의 OVS Bridge의 네트워크(10.0.0.0/8)가 상단의 Linux Bridge(docker0)의 네트워크(10.x.0.0/16)를 포함하는 구조를 가져야 한다는 것이다(참고: http://www.ipaddressguide.com/cidr).



사전 준비

  • Flat Network로 연결된 Docker Host(VM) 2대
  • Docker Host #1: CentOS 7.3 Minimal Server(1611 ver), 192.168.10.163/16
  • Docker Host #2: CentOS 7.3 Minimal Server(1611 ver), 192.168.10.164/16



Docker & Open vSwitch 설치와 firewall 설정


* Docker 호스트에 EPEL 리포지터리와 RDO 프로젝트를 통해서 Docker와 Open vSwitch를 설치

(Open vSwich의 별도 설치 방법에 대해서는 http://bryan.wiki/276 참고)

yum clean all
yum install -y epel-release https://www.rdoproject.org/repos/rdo-release.rpm
yum install -y firewalld docker openvswitch bridge-utils
yum update -y
systemctl start openvswitch firewalld
systemctl enable openvswitch firewalld


* VxLAN 터널링을 위한 패킷은 UDP 4789, 8472 포트를 통해 전송된다. firewall 설정에서 이 2개 포트를 모두 개방

firewall-cmd --add-port=4789/udp --add-port=8472/udp
firewall-cmd --permanent --add-port=4789/udp --add-port=8472/udp


* trusted 영역에 대해 10.*.*.* 대역의 통신이 모두 가능하도록 개방(docker0 네트워크 10.x.0.0/16 을 서브넷으로 포함하는 상위 네트워크의 예: 10.0.0.0/8)

* 다른 예: docker0 네트워크를 172.17.0.0/16 이나 172.18.0.0/16 ... 등으로 할 경우에는 상위 네트워크를 172.16.0.0/12 로 하면 된다

firewall-cmd --permanent --zone=trusted --add-source=10.0.0.0/8


각각의 도커 호스트에서 Docker 네트워크 설정


* Docker 에서 네트워크가 가능하려면 --net=bridge(기본 모드) 또는 --net=host 옵션으로 컨테이너를 생성하게 된다. 컨테이너 생성시에 별도의 옵션을 지정하지 않으면 bridge 모드가 사용되는데, 여기서는 기본 모드인 bridge 모드를 사용할 것이며, /etc/sysconfig 디렉토리의 docker-network 설정 파일을 다음과 같이 설정한다.

[root@docker01 ~#] /etc/sysconfig/docker-network

DOCKER_NETWORK_OPTIONS="--bip=10.1.0.1/16 --iptables=false --ip-masq=false --ip-forward=true"

[root@docker01 ~#] systemctl restart docker


[root@docker02 ~#] /etc/sysconfig/docker-network

DOCKER_NETWORK_OPTIONS="--bip=10.2.0.1/16 --iptables=false --ip-masq=false --ip-forward=true"

[root@docker02 ~#] systemctl restart docker

--bip 는 Bridge IP 를 뜻하며, 컨테이너의 네트워크에서 자동적으로 10.1.0.1, 10.2.0.1 을 default gateway 로 바라보게 된다.

  


위와 같이 설정하고 Docker 컨테이너를 생성하면, docker01 호스트에서 첫 번째로 만들어지는 컨테이너의 IP는 10.1.0.2 가 될 것이다(docker02 에서는 10.2.0.2).


여기까지 끝이라면 좋겠지만 아쉽게도 그렇지 않다. 2개의 호스트에서 만든 각각의 컨테이너에서 서로 다른 컨테이너로 접속하려 하면? 당연히, 안된다. 서로 다른 네트워크간의 연결은 통로가 열려 있지 않으면 당연히 통신이 되지 않는다(아직 컨테이너 생성까지 진도는 나가지 않았지만, busybox 컨테이너를 docker01, docker02 에서 각각 만들고 docker01 호스트에서 ping 10.2.0.2 해보자. 당연히 안된다).



접속 경로의 연결을 위한 Open vSwitch Bridge와 VxLAN 설정


* docker01 에서는 다음과 같이 Open vSwitch 를 설정

[root@docker01 ~#] ovs-vsctl add-br ovs_sw0

[root@docker01 ~#] ip addr add 10.100.0.1/8 dev ovs_sw0 && ip link set dev ovs_sw0 up

[root@docker01 ~#] ovs-vsctl add-port ovs_sw0 vxlan0 -- set Interface vxlan0 type=vxlan options:remote_ip=192.168.10.164


* docker02 에서는 다음과 같이 Open vSwitch 를 설정

[root@docker02 ~#] ovs-vsctl add-br ovs_sw0

[root@docker02~#] ip addr add 10.100.0.2/8 dev ovs_sw0 && ip link set dev ovs_sw0 up

[root@docker02 ~#] ovs-vsctl add-port ovs_sw0 vxlan0 -- set Interface vxlan0 type=vxlan options:remote_ip=192.168.10.163


위의 그림에서 보듯이 각 Docker 호스트에 sw0 라는 Open vSwitch 브리지간의 터널을 개통하는 과정이다. 주의 깊게 보아야 하는 부분은 vxlan0 라는 VTEP(VxLAN Terminal End Point)가 상대편 Docker 호스트의 네트워크 IP를 바라보게 설정하는 것이다.


여기까지 설정하였다면 그림에서의 하단(Open vSwitch 브리지간)의 터널 개통이 정상적으로 되었는지 확인해 보기로 하자

[root@docker01 ~]# ping 10.100.0.2

PING 10.100.0.2 (10.100.0.2) 56(84) bytes of data.

64 bytes from 10.100.0.2: icmp_seq=1 ttl=64 time=2.43 ms

64 bytes from 10.100.0.2: icmp_seq=2 ttl=64 time=0.418 ms

64 bytes from 10.100.0.2: icmp_seq=3 ttl=64 time=0.333 ms

64 bytes from 10.100.0.2: icmp_seq=4 ttl=64 time=0.354 ms

64 bytes from 10.100.0.2: icmp_seq=5 ttl=64 time=0.410 ms

^C

--- 10.100.0.2 ping statistics ---

5 packets transmitted, 5 received, 0% packet loss, time 4001ms

rtt min/avg/max/mdev = 0.333/0.790/2.438/0.824 ms

10.100.0.1/8 과 10.100.0.2/8 은 동일한 네트워크 대역이고, VxLAN 터널을 통해 서로 연결이 잘 되고 있음을 확인할 수 있다.



Linux Bridge와 Open vSwitch Bridge 간의 연결선, Veth Pair


하단의 터널이 뚫렸으므로 상단의 연결선을 만들어 보자. Docker container(브리지 모드일 경우)는 기본적으로 Linux Bridge와 자동으로 연결이 맺어 진다. 즉, Docker 엔진이 docker0 라는 디폴트 브리지(Linux Bridge)에 Docker 컨테이너의 네트워크 디바이스를 내부적으로 연결시켜 주게 된다.


그렇다면 우리는, 그 아래의 docker0와 sw0 사이의 연결을 맺어 주기만 하면 되는데, 이를 위해 VETH pair 라고 하는, 양쪽 끝이 연결되어 있는 연결 쌍(pair)이라는 도구를 사용해서 위 아래의 접점에 붙여 주면 된다.


그림에서 처럼, docker0(Linux Bridge)와 sw0(Open vSwitch Bridge)간의 연결을, Veth Pair 라고 하는 연결선을 통해서 맺어 줄 수 있다. 다음과 같이 해 보자. 이 연결선의 End Point(끝점) 들 중에서 veth_d0는 docker0 쪽으로, veth_sw0는 sw0 쪽으로 연결한다.

[root@docker01 ~]# ip link add veth_sw0 type veth peer name veth_d0

[root@docker01 ~]# ovs-vsctl add-port ovs_sw0 veth_sw0

[root@docker01 ~]# brctl addif docker0 veth_d0

[root@docker01 ~]# ip link set dev veth_sw0 up

[root@docker01 ~]# ip link set dev veth_d0 up


docker02 에서도 위와 같이 설정해 주자. End Point 들의 이름은 docker01 에서와 다르게 주어도 무관하지만 script 작성 등의 자동화를 위해서는 최대한 일관성 유지를 하는 것이 좋다.

[root@docker02 ~]# ip link add veth_sw0 type veth peer name veth_d0

[root@docker02 ~]# ovs-vsctl add-port ovs_sw0 veth_sw0

[root@docker02 ~]# brctl addif docker0 veth_d0

[root@docker02 ~]# ip link set dev veth_sw0 up

[root@docker02 ~]# ip link set dev veth_d0 up


각각의 Docker 호스트에서 다음과 같이 실행해서 브리지 설정을 확인한다.

[root@docker01 ~]# ovs-vsctl show

f3c8825d-73ba-4ee5-a136-3db14a32e990

    Bridge "ovs_sw0"

        Port "veth_sw0"

            Interface "veth_sw0"

        Port "ovs_sw0"

            Interface "ovs_sw0"

                type: internal

        Port "vxlan0"

            Interface "vxlan0"

                type: vxlan

                options: {remote_ip="192.168.10.164"}

    ovs_version: "2.5.2"


[root@docker01 ~]# brctl show

bridge name bridge id   STP enabled interfaces

docker0   8000.0242f84de852 no    veth_d0



Docker 컨테이너를 만들고 L3 연결 확인


docker01, docker02 호스트에서 각각 컨테이너 생성, 네트워크 확인

[root@docker01 ~]# docker run -i -t busybox /bin/sh

/ # ifconfig

eth0      Link encap:Ethernet  HWaddr 02:42:0A:01:00:02  

          inet addr:10.1.0.2  Bcast:0.0.0.0  Mask:255.255.0.0

          inet6 addr: fe80::42:aff:fe01:2/64 Scope:Link

          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

          RX packets:88 errors:0 dropped:0 overruns:0 frame:0

          TX packets:51 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:0 

          RX bytes:7240 (7.0 KiB)  TX bytes:4470 (4.3 KiB)


lo        Link encap:Local Loopback  

          inet addr:127.0.0.1  Mask:255.0.0.0

          inet6 addr: ::1/128 Scope:Host

          UP LOOPBACK RUNNING  MTU:65536  Metric:1

          RX packets:0 errors:0 dropped:0 overruns:0 frame:0

          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:1 

          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


[root@docker02 ~]# docker run -i -t busybox /bin/sh

/ # ifconfig

eth0      Link encap:Ethernet  HWaddr 02:42:0A:02:00:03  

          inet addr:10.2.0.3  Bcast:0.0.0.0  Mask:255.255.0.0

          inet6 addr: fe80::42:aff:fe02:3/64 Scope:Link

          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

          RX packets:24 errors:0 dropped:0 overruns:0 frame:0

          TX packets:20 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:0 

          RX bytes:1880 (1.8 KiB)  TX bytes:1656 (1.6 KiB)


lo        Link encap:Local Loopback  

          inet addr:127.0.0.1  Mask:255.0.0.0

          inet6 addr: ::1/128 Scope:Host

          UP LOOPBACK RUNNING  MTU:65536  Metric:1

          RX packets:0 errors:0 dropped:0 overruns:0 frame:0

          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:1 

          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


docker01 호스트의 컨테이너에서 docker02 호스트의 컨테이너와 ping 테스트

/ # ping 10.2.0.2

PING 10.2.0.2 (10.2.0.2): 56 data bytes

64 bytes from 10.2.0.2: seq=0 ttl=63 time=1.730 ms

64 bytes from 10.2.0.2: seq=1 ttl=63 time=0.528 ms

64 bytes from 10.2.0.2: seq=2 ttl=63 time=0.321 ms



마지막으로, 실제로 2개의 컨테이너간의 연결이 확실한지를 nc(NetCat)을 통해서 테스트해 보자. busybox 에 기본적으로 있는 기능이다.


docker01 호스트의 컨테이너에서 8080 포트로 nc 리스닝

/ # nc -l -p 8080 0.0.0.0

Test from docker02 container...

/ # 


docker02 호스트의 컨테이너에서 docker01 호스트의 컨테이너로 nc 접속(메시지 전송)

/ # echo "Test from docker02 container..." | nc 10.1.0.2 8080

/ #




컨테이너에서 외부 네트워크로 접속하려면?


서로 다른 호스트에 각각 분리되어 있는 컨테이너간의 네트워크 연결은 가능해 졌지만, 원래의 Docker 네트워크 구조를 바꿔 버렸기 떄문에 현재의 설정으로는 컨테이너 사이의 네트워크만 가능할 뿐이다. 즉 외부 네트워크(192.168.10.* 를 벗어난)으로의 접속은 불가능한 상태다.


이 때에는 다음과 같이 firewall-cmd 를 통해서 목적지가 다를 경우 masquerading(NATting)이 되도록 설정해 주면 된다(각 docker 호스트에서 실행).


* (참고) 컨테이너 prompt 에서 exit 또는 ctrl-d 로 빠져나오게 되면 컨테이너가 생성되면서 실행된 터미널이 종료되면서 컨테이너는 STOP 상태로 빠지게 된다. 이 컨테이너에 재접속 하려면 "docker start" 로 다시 동작시켜야 하는데, STOP 되지 않도록 빠져나오려면 ctrl-p 와 ctrl-q 를 차례로 누르면 되며, 이후에 "docker attach" 로 재접속할 수 있게 된다.

[root@docker01 ~]# firewall-cmd --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.0.0.0/8 ! -d 10.0.0.0/8 -j MASQUERADE

success

[root@docker01 ~]# firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.0.0.0/8 ! -d 10.0.0.0/8 -j MASQUERADE

success

[root@docker01 ~]# 

[root@docker01 ~]# docker ps

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

871a0f2765b6        busybox             "/bin/sh"           3 days ago          Up 3 days                               ecstatic_hopper

[root@docker01 ~]# docker attach 871a0f2765b6

/ # ping yahoo.com

PING yahoo.com (206.190.36.45): 56 data bytes

64 bytes from 206.190.36.45: seq=0 ttl=48 time=158.904 ms

64 bytes from 206.190.36.45: seq=1 ttl=48 time=159.080 ms

64 bytes from 206.190.36.45: seq=2 ttl=48 time=159.194 ms

64 bytes from 206.190.36.45: seq=3 ttl=48 time=164.183 ms

^C

--- yahoo.com ping statistics ---

4 packets transmitted, 4 packets received, 0% packet loss

round-trip min/avg/max = 158.904/160.340/164.183 ms



- Barracuda -



[관련 글]

2017/06/08 - [Technical/Cloud, 가상화] - Open vSwitch, VxLAN을 이용한 분산 Docker 컨테이너 간의 네트워크 연결(2/2)


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

Barracuda

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


Open vSwitch는 SDN을 구현하는 대표적 오픈소스 기술 중의 하나이다. Open vSwitch는 분산된 노드들간의 가상 터널을 구현하는 3가지 방법(GRE, VxLAN, IPSec)을 모두 지원하는데, 본 편은 다음에 이어질 포스팅에서 다룰 VxLAN에 대한 사전 준비 과정에 해당한다고 할 수 있다. 대상 OS는 CentOS 7.x 로 하고, Open vSwitch 를 설치하는 방법은 크게 3가지 정도를 생각해 볼 수 있겠다. 소스를 빌드하거나 RPM 빌드 또는 레드햇의 Epel 레포지터리를 통해 yum 으로 설치하는 방법이 있다. 여기서는 뒤의 2가지를 다뤄두도록 한다(자세한 설명은 생략하고 필수적으로 수행되는 script 위주로 note 함).


이미지 출처: openvswitch.org


RPM 빌드를 통한 설치


* 다운로드 주소는 openvswitch.org의 LTS(Long Term Service) series 에 해당하는 2.5.x 을 찾아서 주소를 복사해 오면 된다

# yum clean all

# yum update -y

# yum -y install wget bridge-utils openssl-devel gcc make python-devel openssl-devel kernel-devel graphviz kernel-debug-devel autoconf automake rpm-build redhat-rpm-config libtool python-twisted-core python-zope-interface PyQt4 desktop-file-utils libcap-ng-devel groff checkpolicy selinux-policy-devel

# adduser ovs

# su - ovs

ovs ~$ mkdir -p rpmbuild/SOURCES

ovs ~$ cd rpmbuild/SOURCES/

ovs ~$ wget http://openvswitch.org/releases/openvswitch-2.5.2.tar.gz

ovs ~$ tar xvzf openvswitch-2.5.2.tar.gz 

ovs ~$ rpmbuild -bb --nocheck openvswitch-2.5.2/rhel/openvswitch-fedora.spec

ovs ~$ exit

# yum localinstall /home/ovs/rpmbuild/RPMS/x86_64/openvswitch-2.5.2-1.el7.centos.x86_64.rpm -y

# systemctl start openvswitch

# systemctl enable openvswitch

# ovs-vsctl -V

ovs-vsctl (Open vSwitch) 2.5.2

Compiled May 31 2017 22:33:26

DB Schema 7.12.1



EPEL 을 통한 yum 설치


* EPEL(Extra Packages of Enterprise Linux): 레드햇에서 제공하는 별도의 패키지 저장소(주로 레드햇 계열인 RHEL, CentOS, Fedora 용도)

# yum install -y epel-release https://www.rdoproject.org/repos/rdo-release.rpm

# yum install -y openvswitch bridge-utils

# yum update -y

# systemctl start openvswitch

# systemctl enable openvswitch



- Barracuda -


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

Barracuda

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



2회(#2/3) 에서 이어지는 내용이다 


  • GitHub 를 사용한 개발과정 맛보기 #2 - WordPress Sample & MySQL(PHP)

이번에는 OpenShift GUI Console 이 아닌 명령어 방식으로 oc(OpenShift Client)를 사용해 보겠다. Master Node로 ssh 로그인을 하고 새로운 프로젝트를 생성한다. OpenShift Console에서 실시간으로 확인 가능하다


GitHub.com의 Wordpress project URL(https://github.com/DragOnMe/WordPress.git)을 복사한다


oc 명령으로 php:latest 도커이미지와 WordPress 소스 repository를 지정하여 새로운 App 서비스를 프로젝트에 추가한다. 진행 상황은 텍스트에 나온 설명대로 'oc logs -f bc/어플리케이션이름' 을 수행해 보거나, 앞서와 마찬가지로 GUI를 통해 Build Log 상태를 확인할 수도 있다 


OpenShift Console 화면에서 새로 만든 프로젝트 클릭


WordPress 실행을 위한 PHP 웹서버 부분은 Build & Deploy 가 완료되었고 하나의 Pod가 기동되어 있다


GUI console에서와 달리, 명령어 방식에서는 서비스에 접속 가능하도록 expose 명령으로 주소를 할당해야 한다. 이제 MySQL DB를 준비해 보자


상단의 프로젝트 타이틀 우측 'Add to project' 클릭


Technologies 부분의 'Data Stores' 클릭


'MySQL (Ephemeral)' 선택


계정, 암호 등은 자동 생성되도록 비워 두고 'Create' 클릭(기억할 수 있는 암호와 계정을 정하고 직접 입력해도 된다)


계정, 암호 등은 MySQL 컨테이너 기동 시에 정확히 입력해야 하므로 잘 기억 또는 메모해 둔다(긁어서 복사해 두자). 'Continue to overview' 클릭


위쪽에 새로 만든 MYSQL EPHEMERAL 컨테이너 서비스가 기동되어 pod 1개가 만들어져 있다. 이제 PHP 웹을 실행해 보기 위해 Route(접속 URL)을 클릭


WordPress 초기 셋업(설치) 화면이 연결되었다. 'Let's go!' 클릭


Database Host 에는 OpenShift 프로젝트에 존재하는 서비스명(mysql)을 입력한다. 'Submit' 클릭


'Run the install' 클릭


제목 등을 적당히 입력하고 'Install WordPress' 클릭. 다음 화면에서 'Log In' 클릭, 입력한 로그인 계정으로 로그인


WordPress 대쉬보드 화면 접속 성공!



  • Auto-Scaling 의 사용(Hawkular Metrics)

#1. 수동 scaling - 새로운 프로젝트 생성(Create) 


Autoscale 테스트를 위한 PHP web 을 구동할 예정이다. PHP 선택


언어는 크게 상관이 없다. PHP 5.6 으로 선택하고 'Select'


github.com 프로젝트로 간단한 PHP 페이지 프로젝트를 준비한다. 단순히 phpinfo() 를 수행하는 웹사이트이다. Clone or download 를 통해 URL(https://github.com/DragOnMe/php_for_autoscale.git) 복사


Git Repository URL에 위에서 복사한 URL을 paste(Create)


Continue to overview 로 Overview 화면으로 이동


서비스 내에 Pod 가 하나 만들어지고 Web 서비스 준비 완료. 상단 Routes 주소 클릭


웹서비스 정상 접속 완료


Pod 를 수동으로 Scaling 해보자. Overview 로 돌아와서 Pod 를 클릭


현재 기동되어 있는 단 하나의 Pod 가 보일 것이다. Pod 이름 클릭


동일한 Pod가 여러 개 생길 수 있으므로 Pod 이름은 '서비스명-빌드번호-b9t2h' 와 같은 형태로 자동으로 생성된다. Pod의 상태를 확인해 보면 내부 사설 IP 10.130.0.27 이며 node02 에서 동작하고 있음을 볼수 있다


Pod 우측의 위아래 버튼을 누르면 Pod의 갯수가 증가/감소하는 것을 볼 수 있다


Pod를 2개로 만든 상태에서 Pod 버튼을 클릭하면 2개의 Pod 가 기동되어 있음을 볼 수 있다


새로 만들어진 Pod 를 클릭해 보면, IP 10.129.0.13 이며 node01 에서 동작하고 있다



#2. 자동 scaling, HPA(Horizontal Pod autoscale) 라고 하며, 본 글 작성시 사용중인 Origin 1.4 버전에서는 Hawkular Metrics 가 비교적 안정적으로 통합되어 오토스케일을 위한 추가 설정의 부담이 많이 줄어들었다 - 새로운 프로젝트 생성(Create), 앞서 #1 과 마찬가지로  https://github.com/DragOnMe/php_for_autoscale.git 저장소 주소를 사용하는데, 이번에는 프로젝트 이름만 Auto-2 로 구분가능하도록 진행하자


* 여기서 중요하게 확인해야 할 것은 위의 터미널에 나와 있는 oc get pods 명령어의 결과에서처럼, metric 모니터링을 위한 3개의 기본 pod(hawkular metrics/cassandra, heapster) 가 반드시 Running 상태가 되어 있어야 한다는 것이다 


서비스를 추가하는 화면에서 아래로 스크롤해 내려간다


Scaling 타이틀 아래에 Strategy 부분을 Automatic 으로 바꾸고, 아래의 설정 값들을 적당히 입력한다


조금 더 아래로 내려 가면 Resource Limits 부분이 나온다. 여기에 세부 Metric 설정 값들을 입력해야만(자세한 설정 방법은 여기 참고), 비로소 해당 서비스를 자동으로 Scale-out/in 할 수 있게 된다(Create 클릭)


이전 프로젝트들의 Summary 화면과 다른 부분을 볼 수 있다. 우측 상단의 서비스 URL을 클릭해서 해당 서비스 페이지를 접속해보고, 화면 refresh 를 연속으로 몇 차례 시도한 후, 이 화면으로 돌아오면 Pod 왼쪽의 그래프 부분이 바뀌어 부하 발생에 바뀌어 나타나는 것을 볼 수 있다


osmaster 내부가 아닌 외부 시스템의 터미널 창에서 Apache ab 를 실행하여, 서비스 URL 페이지에 대해 다량의 접속 부하를 발생시키면, OpenShift Summary 화면의 그래프가 바뀌며, Pod가 최대 갯수인 4개까지 자동으로 늘어나는(scaled-out) 것을 볼 수 있다


웹페이지 부하를 멈춘 상태에서 몇 분 정도 지나면 자동으로 최소 Pod 갯수인 1개까지 줄어(scaled-in) 있는 것을 볼 수 있다



  • Persistent-Volume 의 사용

#1. github.com 에서 simple-file-manager 리포리터리의 URL 복사


새로운 프로젝트를 만들고 PHP 언어를 선택. Git URL에 위에서 복사한 주소 paste


빌드 / 배포가 완료 되면 오픈소스로 공개된 간단한 파일 업다운로드 서비스가 기동될 것이다. 새로운 파일을 업로드해 보자


Overview를 통해 Pod를 선택하고 Terminal 로 들어가 보면 새로 업로드한 파일이 보인다. 그러나 이 파일은 Pod(또는 컨테이너)가 없어지면 같이 사라지게 된다(/tmp 아래의 일회용 스토리지 공간에 저장)


* Overview 화면에서 Pod를 1개 늘리고, Terminal 로 들어가서 ls 를 해 보면 새로 만들어진 Pod에는 위에서 업로드한 파일이 보이지 않을 것이다. Pod 별로 각각 일회용 스토리지 영역이 할당되기 때문이다


 

#2 이번에는 OpenShift 내에서 미리 설정된 Persistent Volume(NFS 볼륨)을 사용하여 컨테이너에 직접 마운트해서 사용해 본다. 위 #1과 마찬가지로 새로운 프로젝트를 만들고 https://github.com/DragOnMe/simple-file-manager.git 리포지토리를 연결한다


Deployments 화면 아래의 Add storage 클릭


"프로젝트의 배포 설정에 추가할 persistent volume 요청이 없으니 추가" 하라고 한다(Create storage 클릭)


[Volume Claim 단계] 앞서 Persistent Volume을 ReadWriteMany 모드로 만들었으므로 Access Mode는 RWX 를 선택하고 볼륨 크기를 적당히 지정(미리 만든 각 PV들은 400M 이므로 그 이하의 크기)하고 Create


[Add Storage: 스토리지 할당 단계] 앞의 Volume Claim 에 대해 pv010 볼륨이 자동으로 할당되었다. Mount Path 는 컨테이너 Application 내부에서 사용하는 Mount Point의 Path(simple-file-manager에서 사용하는 data path는 /opt/app-root/src 이다) 를 입력한다


다시 위로 스크롤 해서 Deploy 를 수행한다


Pod 에 터미널로 접속해서 새로운 파일을 하나 만들자. 파일 업로드를 흉내 내 보는 것


Overview 로 돌아가서 Pod 를 하나 늘려두자


새로이 만들어진 Pod 에서 터미널 접속 후 ls 로 확인해 보면 앞에 만들어져 있던 Pod 에서 생성한 파일이 보인다. 두개의 Pod 가 OpenShift 가 제공하는  PV를 공유하고 있음을 알 수 있다


좌측 Storage 메뉴를 통해, 현재 프로젝트에 할당된 스토리지 정보를 확인할 수 있느. 명령어 방식으로 system:admin 계정으로 oc get pv 명령어를 통해서 전체 시스템에서 사용 가능한 PV 정보를 확인힐 수도 있다



이로써 3회에 걸친 OpenShift Origin v3 에 대한 설치와 소프트웨어 엔지니어 입장에서의 간단한 설정 & 사용 방법에 대해 알아 보았습니다. 되도록 간단히 요점 위주로 작성해 두려 했으나, 직접 수행해 본 내용을 꼼꼼히 빠짐 없이 기재하고 설명을 달다 보니 분량이 제법 늘어나 있어서 다소 읽기에 부담이 될지도 모르겠습니다. 불필요한 부분은 알아서 스킵하시고 참고해 주시길~ ^^


혹시 궁금한 점이 있거나, 지적할 부분이 있다면 언제든 댓글로 컨택해 주시기 부탁 드립니다. - Barracuda -





[연관되는 글]

[OpenShift v3 #1] Origin all-in-one, CentOS 7 기반 단일서버 설치 사용법(1/3)

[OpenShift v3 #2] Origin 3-node, CentOS 7 기반 3노드 설치, 사용 방법(2/3)


- Barracuda -



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

Barracuda

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


노트북에 서로 다른 사설IP 대역(노트북의 랜카드와 동일한 브리징 네트워크의 IP가 아닌, 내부 사설 네트워크)로 가상네트워크를 구성해서 VM을 만들 경우가 있다. 이 경우 대개 고정IP와 함께 고정된 default gateway를 설정하게 되는데, 사무실과 집을 오가다 보면 매번 VM들의 default gateway를 변경해 주어야 한다.


이런 번거로움을 피하기 위해 VirtualBox 내에 Vyatta Router VM을 만들고, 각 VM들의 사설 IP대역에 맞는 interface(eth1, eth2, eth3, ...)를 각각 설정한 다음, VM에서는 Vyatta Router의 interface IP를 default gateway 로 설정해 둔다. 물론 Vyatta Router의 eth0는 인터넷이 가능한 IP 대역으로 Bridged Network로 구성하고, eth1/eth2/eth3 으로부터 eth0로 라우팅을 해 주어야 각 VM에서 Vyatta Router를 통해 외부로 접속이 가능할 것이다.


이 방법을 응용한다면 VMWare용 Vyatta Router Virtual Appliance나 다른 가상화 솔루션들을 사용할 경우나, Vyatta Router를 사용한 간단한 내부 네트워크를 동적으로 구성할 때도 도움이 될 만 하겠다.


아래에 그 방법에 대해 정리해 둔다.




Vyatta Router VM의 설치


  • Vyos.io 사이트에서 http://packages.vyos.net/iso/release/1.1.7/ 로 접속하여 iso 파일(vyos-1.1.7-amd64.iso)을 다운로드
    - 필요에 따라 다른 iso를 골라서 설치 사용하거나 ova 파일 자체를 appliance로 올려서 쓸수도 있다 
  • Virtual Box에서 linux 머신(CPU1, Ram 512, Disk 2GB)을 생성하고 ISO파일을 CD에 장착하고 Vyatta OS를 설치
    - VirtualBox에서의 VM 생성과 설치 방법에 대해서는 따로 설명하지 않는다
  • 사용할 네트워크에 맞게 네트워크 장치(NIC)를 생성한다. 위 그림에서와 같이 작업을 하려면 다음의 구성정보와 그림을 참고한다
    - 어댑터1: 브리지 어댑터, 무작위 모드: 모두 허용
    - 어댑터2, 3, 4: 호스트 전용 어댑터(사전에 만든 호스트 전용 네트워크 이름 지정, vboxnet0), 무작위 모드: 모두 또는 가상머신에 허용, 어댑터종류:아무거나...반가상네트워크도 가능)


어댑터2,3,4는 모두 동일하게 설정



Vyatta Router Interface 및 라우팅 설정



  • Vyos 설치가 완료되고 리부트한 후, 기본 설정된 관리자 계정 vyos(암호: vyos) 로 로그인한다
  • 초기 설정에 대해 configure, set 등의 명령으로 각각 인터페이스 생성과 설정을 수행하고 확인한다. configure 이후의 설정을 저장하려면 commit 다음에 save 를 반드시 하여야 한다

vyos@vyos:~$ configure

vyos@vyos# set interfaces ethernet eth0 description OUTSIDE

vyos@vyos# set interfaces ethernet eth0 address 192.168.0.201/16

vyos@vyos# set system gateway-address 192.168.0.1

vyos@vyos# set system domain-name 8.8.8.8

vyos@vyos# set service ssh port '22'

vyos@vyos# commit

vyos@vyos# save

Saving configuration to '/config/config.boot'...

vyos@vyos# exit

vyos@vyos:~$ show interfaces
Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down
Interface        IP Address                        S/L  Description
---------        ----------                        ---  -----------
eth0             192.168.0.201/16                  u/u  OUTSIDE
lo               127.0.0.1/8                       u/u  
                 ::1/128
vyos@vyos:~$ 
vyos@vyos:~$ configure
vyos@vyos# set interfaces ethernet eth1 description 10-NETWORK
vyos@vyos# set interfaces ethernet eth1 10.255.10.1/24
vyos@vyos# set interfaces ethernet eth1 description 20-NETWORK
vyos@vyos# set interfaces ethernet eth1 10.255.20.1/24
vyos@vyos# set interfaces ethernet eth1 description 30-NETWORK
vyos@vyos# set interfaces ethernet eth1 10.255.30.1/24

vyos@vyos# save

Saving configuration to '/config/config.boot'...

vyos@vyos# exit

vyos@vyos:~$ show interfaces
Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down
Interface        IP Address                        S/L  Description
---------        ----------                        ---  -----------
eth0             192.168.0.201/16                  u/u  OUTSIDE 
eth1             10.255.10.1/24                    u/u  10-NETWORK 
eth2             10.255.20.1/24                    u/u  20-NETWORK 
eth3             10.255.30.1/24                    u/u  30-NETWORK 
lo               127.0.0.1/8                       u/u  
                 ::1/128


  • 내부 사설망과 연결되는 개별 router 포트(eth1, eth2, eth3) 에 대해 다음과 같이 설정하고 저장한다

내부 사설망과 연결되는 개별 router port(eth1, 2, 3)에 대해 다음과 같이 설정한다

vyos@vyos:~$ configure

vyos@vyos# set nat source rule 10 source address 10.255.10.0/24

vyos@vyos# set nat source rule 10 outbound-interface eth0

vyos@vyos# set nat source rule 10 protocol 'all'

vyos@vyos# set nat source rule 10 translation address masquerade

vyos@vyos# set nat source rule 20 source address 10.10.255.0/24

vyos@vyos# set nat source rule 20 outbound-interface eth0

vyos@vyos# set nat source rule 20 protocol 'all'

vyos@vyos# set nat source rule 20 translation address masquerade

vyos@vyos# set nat source rule 30 source address 10.255.30.0/24

vyos@vyos# set nat source rule 30 outbound-interface eth0

vyos@vyos# set nat source rule 30 protocol 'all'

vyos@vyos# set nat source rule 30 translation address masquerade

vyos@vyos# commit

vyos@vyos# save

Saving configuration to '/config/config.boot'...

vyos@vyos# show nat source rule 10

 outbound-interface eth0

 protocol all

 source {

     address 10.255.10.0/24

 }

 translation {

     address masquerade

 }

[edit]

vyos@vyos# exit


  • Router의 정상작동은, 내부 사설망을 사용할 VM(네트워크 어댑터는 호스트 전용, Vyatta Router 의 eth1 등과 동일한 방식의 어댑터)을 생성하고 default gateway를 10.255.10.1  등 Router port의 IP로 설정하여 외부 인터넷으로 ping 이 도달되는지 확인해 보면 된다
  • Vyatta Router를 재시작하기 위해서는 명령어 상태에서 reboot 명령어를, 끄기 위해서는 단순히 poweroff 명령어를 실행한다


사설망 외부에서 내부로의 직접 접근(Port Forwarding)

  • 공유기를 통한 외부에서 내부로의 접근 방식과 동일한 개념으로 Port fordwarding을 설정하면 된다
  • 설정 방법과 과정을 예로 들어보면 아래와 같다(192.168.0.201:65022 -> 10.255.10.100:22, TCP/UDP DNAT 포워딩)
vyos@vyos:~$ configure
vyos@vyos# set nat destination rule 100 description PORT_FORWARD_TEST
vyos@vyos# set nat destination rule 100 destination address '192.168.0.201'
vyos@vyos# set nat destination rule 100 destination port 65022
vyos@vyos# set nat destination rule 100 inbound-interface 'eth0'
vyos@vyos# set nat destination rule 100 protocol tcp_udp
vyos@vyos# set nat destination rule 100 translation address '10.255.10.100'
vyos@vyos# set nat destination rule 100 translation port '22'
vyos@vyos# commit
vyos@vyos# save
Saving configuration to '/config/config.boot'...
vyos@vyos# show nat destination rule 100
 description SIP_FORWARD_TEST
 destination {
     address 192.168.0.201
     port 65022
 }
 inbound-interface eth0
 protocol tcp_udp
 translation {
     address 10.255.10.100
     port 22
 }
[edit]
vyos@vyos# exit


네트워크 대역에 DHCP 설정하기

* 필요에 따라 특정 대역에 대해 DHCP를 설정해야 할 필요가 있을 경우 다음과 같이 설정한다(추가: 2017-09)
vyos@vyos:~$ configure
vyos@vyos# set service dhcp-server shared-network-name NET10_POOL subnet 10.255.10.0/24 start 10.255.10.2 stop 10.255.10.80
vyos@vyos# set service dhcp-server shared-network-name NET10_POOL subnet 10.255.10.0/24 default-router 10.255.10.1
vyos@vyos# set service dhcp-server shared-network-name NET10_POOL subnet 10.255.10.0/24 dns-server 10.255.10.100
vyos@vyos# commit
vyos@vyos# save
vyos@vyos# show service dhcp-server
 disabled false
 shared-network-name NET10_POOL {
     authoritative disable
     subnet 10.255.10.0/24 {
         default-router 10.255.10.1
         dns-server 10.255.10.100
         lease 86400
         start 10.255.10.2 {
             stop 10.255.10.80
         }
     }
 }
[edit]
vyos@vyos# exit


한 걸음 더...

  • 노트북을 사용하는 네트워크 환경이 바뀌었는데 Vyatta Router의 eth0 주소와 각 포트의 라우팅 정보를 수시로 바꿔 주어야 하는 것은 상당히 불편하고 귀찮은 일일 것이다. 사실 본 글의 라우팅 구조를 생각해 낼 떄부터 이 내용을 결론으로 쓰기 위한 것이었는데 ...... 
    - VirtualBox VM관리 화면에서 Vyatta Router VM을 끈 후 복제(Vyatta Router Home 등의  다른 이름으로)
    - 복제할 때 Mac 주소를 자동생성하게(default이지만, 확인 필요) 설정, 단 다음 단계에서 링크가 아닌 '완전한 복제' 실행


     - 새로 만들어진 Vyatta Router VM에 로그인하여, 앞서 수행했던 configure 과정과 비슷한 내용을 다시 수행하는 것이 아니라 vi등의 편집기로 /config/config.boot 내용 중 원하는 부분(외부와 연결되는 eth0 네트워크 정보, default gateway, 4개의 NIC에 대한 MAC 주소-VirtualBox에서 자동할당한 MAC 주소)를 수정하고 reboot 하면 된다. 참고로 아래에 config.boot 파일의 수정해야 할 부분을 붉은 글씨로 따로 표시하였다


vyos@vyos:~$ cat /config/config.boot 

interfaces {

    ethernet eth0 {

        address 192.168.219.201/24

        description OUTSIDE

        duplex auto

        hw-id 08:00:27:8e:73:7a

        smp_affinity auto

        speed auto

    }

    ethernet eth1 {

        address 10.255.10.1/24

        description 10-NETWORK

        duplex auto

        hw-id 08:00:27:b5:91:0e

        smp_affinity auto

        speed auto

    }

    ethernet eth2 {

        address 10.255.20.1/24

        description 20-NETWORK

        duplex auto

        hw-id 08:00:27:6b:38:52

        smp_affinity auto

        speed auto

    }

    ethernet eth3 {

        address 10.255.30.1/24

        description 30-NETWORK

        duplex auto

        hw-id 08:00:27:60:d7:dd

        smp_affinity auto

        speed auto

    }

    loopback lo

...

}

nat {

    destination {

        rule 100 {

            description PORT_FORWARD_TEST

            destination {

                address 192.168.219.201

                port 65022

            }

            inbound-interface eth0

            protocol tcp_udp

            translation {

                address 10.255.10.100

                port 22

            }

        }

        rule 101 {

            description FORWARD_OSMASTER

            destination {

                address 192.168.219.201

                port 60160

            }

            inbound-interface eth0

            protocol tcp_udp

            translation {

                address 10.255.10.160

                port 22

            }

        }

...

    }

    source {

        rule 10 {

            outbound-interface eth0

            protocol all

            source {

                address 10.255.10.0/24

            }

            translation {

                address masquerade

            }

        }

        rule 20 {

            outbound-interface eth0

            protocol all

            source {

                address 10.255.20.0/24

            }

            translation {

                address masquerade

            }

        }

        rule 30 {

            outbound-interface eth0

            protocol all

            source {

                address 10.255.30.0/24

            }

            translation {

                address masquerade

            }

        }

    }

}

service {

    ssh {

        port 22

    }

}

system {

    config-management {

        commit-revisions 20

    }

    domain-name test.local

    gateway-address 192.168.219.1

    host-name vyos

...

vyos@vyos:~$ 



  • 이렇게 한다면, 업무 환경의 네트워크에 따라 Vyatta Router만 바꿔서 사용하므로, 수시로 업무 환경에 따라 VM의 네트워크 정보를 바꿔 주어야 했던 불편함은 깔끔하게 해결되지 않을까 한다



- Barracuda -


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

Barracuda

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


지난 회에서 OpenShift Origin v3의 단일서버 구성 설치와 간단한 사용법을 정리하였다, 이번 회에서는 3-노드 구성의 OpenShift v3 설치 과정과와 Kubernetes, OpenShift console 및 CLI를 통한 Orchestration과 몇가지의 Use Case를 정리해 두고자 한다(이 글을 마지막으로 업데이트하는 시점에 OpenShift 릴리즈가 1.5.0으로 바뀌었다. 아래 설정 파일에서 v1.5.0으로 표시된 부분 참고=>1.5.0에 버그가 아직 많아 보이고, 결정적으로 1.5.1이 나온 시점에 docker 기반 설치에 오류가 발생하여 1.4.1 버전 기준으로 긴급히 되돌림 2017-05-27).



사전 설정(Pre-requisites)

  • 서버 구성(권장 최소 스펙)
    - OS는 CentOS 7.3(64bit r1611), Minimal Server 버전
    - Master: 4 CPU, 6 G memory, 20G+40G(추가디스크) Disk, 추가디스크는 /mnt/persistent-volumes 에 마운트
    - Node01: 2 CPU, 4 G memory, 20G
    - Node02: 2 CPU, 4 G memory, 20G 

  • Release v1.5.0 부터는 Master부터 순서대로 16G/8G/8G 메모리를 권고하며 충족이 안될 경우 fail 되게 바뀌었다(ansible-playbook 실행시 -e openshift_disable_check=memory_availability 옵션 추가 필요)
  •  내부 테스트 또는 외부 서비스용 별도 DNS 서버 필요(DNS 설정 방식은 이전 포스팅(#1) 참조
    - 단일 노드가 아닌 3-노드 구성에 대한 DNS 설정 부분의 차이점 확인 필요
  • 도메인, IP 주소에 대한 부분은 설치 환경과 요구 사항에 맞게 꼼꼼하게 대조하여 오류가 없도록 진행하는 것이 중요



  • DNS forwarding 을 위한 설정 내용(192.168.0.0/16 네트워크 사용시).


[root@dns ~]# cat /etc/named.conf 

options {

listen-on port 53 { any; };

directory  "/var/named";

dump-file  "/var/named/data/cache_dump.db";

statistics-file "/var/named/data/named_stats.txt";

memstatistics-file "/var/named/data/named_mem_stats.txt";


allow-query     { any; };

recursion yes;


// For non-internal domains

forwarders {

168.126.63.1;

8.8.8.8;

};

auth-nxdomain no; # conform to RFC1035

dnssec-enable yes;

// dnssec-validation yes;

dnssec-lookaside auto;


/* Path to ISC DLV key */

bindkeys-file "/etc/named.iscdlv.key";


managed-keys-directory "/var/named/dynamic";


pid-file "/run/named/named.pid";

session-keyfile "/run/named/session.key";

};


logging {

        channel default_debug {

                file "data/named.run";

                severity dynamic;

        };

};


zone "." IN {

type hint;

file "named.ca";

};


include "/etc/named.rfc1912.zones";

include "/etc/named.root.key";


[root@dns ~]# cat /etc/named.rfc1912.zones

...(생략)...

zone "0.in-addr.arpa" IN {

type master;

file "named.empty";

allow-update { none; };

};


zone "test.local" IN {

        type master; // server type

        file "test.local.zone"; // zone file

        allow-update {none;}; // none: no slave to sync

};


zone "168.192.in-addr.arpa" IN { // Reverse

        type master;

        file "/var/named/rev.168.192.in-addr.arpa";

};


[root@dns ~]# cat /var/named/test.local.zone

$ORIGIN .

$TTL 10


test.local.    IN    SOA    dns.test.local.    root.test.local. (

2017031402    ; serial

1D       ; refresh

1H       ; retry

1W      ; expire

3H )    ; min(Negative cache TTL)

test.local.          IN    NS    dns.test.local.

dns.test.local.    IN    A    192.168.0.3


$ORIGIN    test.local.


osmaster        IN      A       192.168.10.160

osnode01        IN      A       192.168.10.161

osnode02        IN      A       192.168.10.162

*.apps          IN      CNAME   osmaster.test.local.


[root@dns ~]# cat /var/named/rev.168.192.in-addr.arpa 

$TTL 10


@    IN    SOA    dns.test.local.    root.test.local. (

2017031403 ; serial

1D ; refresh

1H ; retry

1W ; expire

3H ) ; min(Negative cache TTL)

test.local.          IN    NS    dns.test.local.

dns.test.local.    IN    A    192.168.0.3


@      IN NS      dns.test.local.

3.0    IN PTR    dns.test.local.


160.10    IN PTR    osmaster.test.local.

161.10    IN PTR    osnode01.test.local.

162.10    IN PTR    osnode02.test.local.



  • 준비된 각각의 노드에서 다음과 같은 사전설치 과정을 따른다
[Master노드: osmaster]

# vi /etc/sysconfig/network-scripts/ifcfg-eth0

...

NETMASK=255.255.0.0

DNS1=192.168.0.3

DNS2=8.8.8.8

# hostnamectl set-hostname osmaster

# yum install -y bind-utils

# nslookup osmaster.test.local

# dig yahoo.com a


# cat /etc/selinux/config 

...

SELINUX=enforcing

...

SELINUXTYPE=targeted


# vi /etc/hosts

...

192.168.10.160 osmaster osmaster.test.local

192.168.10.161 osnode01 osnode01.test.local

192.168.10.162 osnode02 osnode02.test.local


# ssh-keygen

# ssh-copy-id root@osmaster.test.local

# ssh-copy-id root@osmaster

# ssh-copy-id root@osnode01.test.local

# ssh-copy-id root@osnode01

# ssh-copy-id root@osnode02.test.local

# ssh-copy-id root@osnode02


[root@osmaster ~]# domain="test.local"


[root@osmaster ~]# for node in {osmaster.$domain,osnode01.$domain,osnode02.$domain}; do ssh root@$node "yum clean all; yum install -y git bind-utils iptables-services net-tools wget bash-completion chrony; yum update -y"; done


[root@osmaster ~]# systemctl enable iptables

[root@osmaster ~]# systemctl start iptables

[root@osmaster ~]# systemctl enable firewalld

[root@osmaster ~]# systemctl start firewalld

[root@osmaster ~]# firewall-cmd --add-service=ntp --permanent

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


[root@osmaster ~]# for node in {osnode01.$domain,osnode02.$domain}; do scp /etc/hosts root@$node:/etc/hosts; done

hosts                                                                                               100%  230     0.2KB/s   00:00    

hosts                                                                                               100%  230     0.2KB/s   00:00    

* 만약 iptables 설치시 "Failed to start iptables.service: Unit is masked." 와 같은 오류가 발생하면 'systemctl unmask iptables" 로 해결한다



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

server time.bora.net iburst

...

# Allow NTP client access from local network.

allow 192.168.0.0/16

...

# Serve time even if not synchronized to any NTP server.

local stratum 10

...


[root@osmaster ~]# systemctl enable chronyd

[root@osmaster ~]# systemctl restart chronyd

[root@osmaster ~]# chronyc tracking

[root@osmaster ~]# chronyc sources -v


[Node01노드: osnode01]

[root@osnode01 ~]# hostnamectl set-hostname osnode01

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

server 192.168.10.160 iburst

...

# Serve time even if not synchronized to any NTP server.

local stratum 10

...


[root@osnode01 ~]# systemctl enable chronyd

[root@osnode01 ~]# systemctl restart chronyd

[root@osnode01 ~]# chronyc tracking

[root@osnode01 ~]# chronyc sources -v



[Node02노드: osnode02]

[root@osnode01 ~]# hostnamectl set-hostname osnode02

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

server 192.168.10.160 iburst

...

# Serve time even if not synchronized to any NTP server.

local stratum 10

...


[root@osnode02 ~]# systemctl enable chronyd

[root@osnode02 ~]# systemctl restart chronyd

[root@osnode02 ~]# chronyc tracking

[root@osnode02 ~]# chronyc sources -v



OpenShift v3 설치 시작


  • EPEL repo를 설정하고 필요한 패키지 설치(여기서부터는 모든 설정 작업을 Master node 에서만 수행)
[Master노드: osmaster]

# yum install -y epel-release

# yum install -y docker

# yum install -y python-cryptography pyOpenSSL.x86_64 java-1.8.0-openjdk-headless python-passlib

# yum install -y python-pip python-devel

# pip install --upgrade pip

# pip install ansible


  • NFS를 persistent storage 로 설치하기 위해 nfs-server 설치, 설정
    - Persistent Volume은 iSCSI, Ceph RBD, GlusterFS, OpenStack Cinder, AWS EBS, Google Cloud Persistent Disk, Fibre Channel 등을 모두 지원
    - 여기서는 여러 인스턴스(컨테이너)들의 공유 사용이 가능함을 확인하기 위해 대표적인 Shared File System인 NFS를 사용

[root@osmaster ~]# yum groupinstall -y file-server

[root@osmaster ~]# systemctl enable rpcbind

[root@osmaster ~]# systemctl enable nfs-server

[root@osmaster ~]# systemctl start rpcbind

[root@osmaster ~]# systemctl start nfs-server

[root@osmaster ~]# iptables-save > pre-nfs-firewall-rules-server

[root@osmaster ~]# iptables -I INPUT -m state --state NEW -p tcp -m multiport --dport 111,892,2049,32803 -s 0.0.0.0/0 -j ACCEPT

[root@osmaster ~]# iptables -I INPUT -m state --state NEW -p udp -m multiport --dport 111,892,2049,32769 -s 0.0.0.0/0 -j ACCEPT

[root@osmaster ~]# service iptables save

[root@osmaster ~]# setsebool -P virt_use_nfs 1


  • Persistent Metrics Volume for Hawkular Metrics
    - Volume 설정 후 nfs-server 서비스가 정상 작동하는지 확인 필요

[root@osmaster ~]# DIR=/mnt/persistent-volumes/metrics

[root@osmaster ~]# mkdir -p $DIR; chmod 777 $DIR

[root@osmaster ~]# chown nfsnobody:nfsnobody $DIR

[root@osmaster ~]# echo "$DIR 192.168.0.0/16(rw,all_squash)" >> /etc/exports

[root@osmaster ~]# systemctl restart nfs-server

[root@osmaster ~]# systemctl status nfs-server


  • OpenShift Ansible 패키지와 configuration 소스 설치, 설정
    - Auto-scale을 위한 hawkular metrics 설치/설정 포함

# git clone https://github.com/DragOnMe/openshift-ansible.git

# git clone https://github.com/DragOnMe/playbook-allinone-cfg.git

# # v1.5 기준, 아래 내용 삭제

# cd openshift-ansible

# git branch -a

* master

  remotes/origin/HEAD -> origin/master

  remotes/origin/master

  remotes/origin/release-1.1

  remotes/origin/release-1.2

  remotes/origin/release-1.3

  remotes/origin/release-1.4

  remotes/origin/release-1.5

  remotes/origin/stage

  remotes/origin/stage-130

  remotes/origin/stage-131

# git checkout remotes/origin/release-1.4

Note: checking out 'remotes/origin/release-1.4'.


You are in 'detached HEAD' state. You can look around, make experimental

changes and commit them, and you can discard any commits you make in this

state without impacting any branches by performing another checkout.


If you want to create a new branch to retain commits you create, you may

do so (now or later) by using -b with the checkout command again. Example:


  git checkout -b new_branch_name


HEAD is now at 7493438... Automatic commit of package [openshift-ansible] release [3.4.1.29-1].

# cd .. 

# vi playbook-allinone-cfg/centos7-3node-playbook.cfg

[OSEv3:children]

masters

nodes


[OSEv3:vars]

ansible_ssh_user=root

deployment_type=origin

containerized=true


# Examples

openshift_install_examples=true


# Persistent metrics over NFS or Dynamic storage

openshift_hosted_metrics_deploy=true

openshift_hosted_metrics_storage_kind=nfs

openshift_hosted_metrics_storage_access_modes=['ReadWriteMany']

openshift_hosted_metrics_storage_nfs_directory=/mnt/persistent-volumes

openshift_hosted_metrics_storage_nfs_options='*(rw,root_squash)'

openshift_hosted_metrics_storage_volume_name=metrics

openshift_hosted_metrics_storage_volume_size=5Gi

openshift_hosted_metrics_storage_host=osmaster.test.local

openshift_hosted_metrics_public_url=https://hawkular-metrics.apps.test.local/hawkular/metrics


# htpasswd authentication

openshift_master_identity_providers=[{'name': 'htpasswd_auth', 'login': 'true', 'challenge': 'true', 'kind': 'HTPasswdPasswordIdentityProvider', 'filename': '/etc/origin/master/htpasswd'}]


# release and image tag

openshift_release=v1.5

openshift_image_tag=v1.5.0

#openshift_pkg_version=-1.5.0


# router node

openshift_hosted_router_selector='region=infra'


# subdomain

openshift_master_default_subdomain="apps.test.local"


# pods placement

osm_default_node_selector='region=primary'


[masters]

osmaster.test.local openshift_public_hostname="osmaster.test.local" openshift_schedulable=true


[nodes]

osmaster.test.local openshift_node_labels="{'region': 'infra', 'zone': 'default'}"

osnode01.test.local openshift_node_labels="{'region': 'primary', 'zone': 'datacenter1'}" openshift_schedulable=true

osnode02.test.local openshift_node_labels="{'region': 'primary', 'zone': 'datacenter2'}" openshift_schedulable=true


[root@osmaster ~]# ansible-playbook -i ./playbook-allinone-cfg/centos7-3node-playbook.cfg ./openshift-ansible/playbooks/byo/config.yml -e openshift_disable_check=memory_availability,disk_availability


PLAY [Create initial host groups for localhost] **************************************************************************************


TASK [include_vars] ******************************************************************************************************************

ok: [localhost]


TASK [Evaluate group l_oo_all_hosts] *************************************************************************************************

ok: [localhost] => (item=osmaster.test.local)

ok: [localhost] => (item=osnode01.test.local)

ok: [localhost] => (item=osnode02.test.local)


PLAY [Create initial host groups for all hosts] **************************************************************************************


TASK [include_vars] ******************************************************************************************************************

ok: [osmaster.test.local]

ok: [osnode01.test.local]

ok: [osnode02.test.local]


PLAY [Populate config host groups] ***************************************************************************************************


TASK [fail] **************************************************************************************************************************

 [WARNING]: when statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: {{ g_etcd_hosts is not

defined }}


skipping: [localhost]

...

...

...

TASK [openshift_excluder : Enable docker excluder] ***********************************************************************************

changed: [osmaster.test.local]

changed: [osnode02.test.local]

changed: [osnode01.test.local]


TASK [openshift_excluder : Check for openshift excluder] *****************************************************************************

ok: [osmaster.test.local]

ok: [osnode01.test.local]

ok: [osnode02.test.local]


TASK [openshift_excluder : Enable openshift excluder] ********************************************************************************

changed: [osnode01.test.local]

changed: [osnode02.test.local]

changed: [osmaster.test.local]


PLAY RECAP ***************************************************************************************************************************

localhost                  : ok=9    changed=0    unreachable=0    failed=0   

osmaster.test.local      : ok=617  changed=104  unreachable=0    failed=0   

osnode01.test.local      : ok=275  changed=41   unreachable=0    failed=0   

osnode02.test.local      : ok=272  changed=39   unreachable=0    failed=0   



  • Containerized 방식의 ansible-playbook을 통한 자동 설치 과정은 하드웨어 성능에 따라 다르겠지만, 대략 10~20분 정도 소요된다
  • [nodes] 영역의 master 부분에서 openshift_schedulable=true 로 하지 않으면 registry 등의 필수적 docker container가 deploy 되지 않아서 OpenShift Cluster가 정상 작동하지 않음에 유의  
  • 오류 없이 성공적으로 수행 된 경우, 최종 출력되는 라인들에서 각 노드 별로 failed=0 값이 출력되어야 한다.
  • 실행 도중 오류가 나는 경우, 진행되었던 설치 과정의 흔적을 다음과 같은 명령으로 Clear 한 후, 각종 문제(주로 selinux 설정, firewalld-iptables, nfs 마운트, URL 오류 관련 문제 등)을 해결하고, 위의 ansible-playbook 명령으로 다시 설치를 시도해야 한다
  • 진행된 모든 과정을 이전 상태로 되돌리기 위해서는 아래 과정을 참고한다

[root@osmaster ~]# ansible-playbook -i ./playbook-allinone-cfg/centos7-3node-playbook.cfg ./openshift-ansible/playbooks/adhoc/uninstall.yml

[root@osmaster ~]# iptables -t nat -F; iptables -F

[root@osmaster ~]# rm -rf /mnt/persistent-volumes/*

[root@osmaster ~]# rm -f /etc/exports


  • 최초 OpenShift 계정으로 guest 계정 생성, 접속 테스트

[root@osmaster ~]# cd /etc/origin/master/

[root@osmaster ~]# cat htpasswd

[root@osmaster ~]# htpasswd -b /etc/origin/master/htpasswd guest openshift

[root@osmaster ~]# oc login

Authentication required for https://osmaster:8443 (openshift)

Username: guest

Password: 

Login successful.


You don't have any projects. You can try to create a new project, by running


    oc new-project <projectname>


[root@osmaster ~]# oc status

Error from server: User "guest" cannot get projects in project "default"

[root@osmaster ~]# oc logout

Logged "guest" out on "https://osmaster:8443"


  • OpenShift용 Persistent Volume 생성(2GB * 15)
    - 사전에 만들어 두고 인스턴스들이 가져다 쓰는 개념으로, 개별 볼륨의 크기와 갯수는 스토리지의 크기와 상황에 맞게 조정
    - Persistent(지속적인) 스토리지에 반대되는 개념은 Ephemeral(일회용의, 단명하는) 스토리지로, Docker가 기본적으로 사용하는 스토리지는 /tmp/ 영역을 사용하는 AUFS(Union 파일시스템) 방식이며, Docker 컨테이너가 삭제되면 이 영역의 데이터도 같이 사라짐
    - 볼륨의 크기 지정 방법과 옵션에 대해서는 Kubernetes Resource Model 을 참고

[root@osmaster ~]# vi playbook-allinone-cfg/pv.json

{

 "apiVersion": "v1",

 "kind": "PersistentVolume",

 "metadata": {

 "name": "pv001"

},

"spec": {

   "capacity": {

   "storage": "2Gi"

   },

   "accessModes": [ "ReadWriteMany" ],

   "nfs": {

      "path": "/mnt/persistent-volumes/pv001",

      "server": "192.168.10.160"

   },

   "persistentVolumeReclaimPolicy": "Recycle"

   }

}


[root@osmaster ~]# oc login -u system:admin

[root@osmaster ~]# for i in `seq -w 001 015`; do \

SHARE=/mnt/persistent-volumes/pv$i; \

mkdir -p $SHARE; chmod 777 $SHARE; \

chown nfsnobody:nfsnobody $SHARE; \

echo "$SHARE 192.168.0.0/16(rw,all_squash)" >>/etc/exports; \

sed s/pv001/pv$i/g /root/playbook-allinone-cfg/pv.json | oc create -f -; \

done

persistentvolume "pv001" created

persistentvolume "pv002" created

persistentvolume "pv003" created

persistentvolume "pv004" created

persistentvolume "pv005" created

persistentvolume "pv006" created

persistentvolume "pv007" created

persistentvolume "pv008" created

persistentvolume "pv009" created

persistentvolume "pv010" created

persistentvolume "pv011" created

persistentvolume "pv012" created

persistentvolume "pv013" created

persistentvolume "pv014" created

persistentvolume "pv015" created

[root@osmaster ~]# oc get pv

NAME      CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM     REASON    AGE

pv001     2Gi        RWX           Recycle         Available                       9s

pv002     2Gi        RWX           Recycle         Available                       8s

pv003     2Gi        RWX           Recycle         Available                       8s

pv004     2Gi        RWX           Recycle         Available                       8s

pv005     2Gi        RWX           Recycle         Available                       8s

pv006     2Gi        RWX           Recycle         Available                       7s

pv007     2Gi        RWX           Recycle         Available                       7s

pv008     2Gi        RWX           Recycle         Available                       7s

pv009     2Gi        RWX           Recycle         Available                       7s

pv010     2Gi        RWX           Recycle         Available                       6s

pv011     2Gi        RWX           Recycle         Available                       6s

pv012     2Gi        RWX           Recycle         Available                       6s

pv013     2Gi        RWX           Recycle         Available                       6s

pv014     2Gi        RWX           Recycle         Available                       5s

pv015     2Gi        RWX           Recycle         Available                       5s


## Openshift 재설치 등을 위한 삭제시에는 oc delete pv <pv-name> 으로 개별 볼륨을 삭제하거나 다음과 같이 한번에 삭제할 수도 있다(데이터 유실에 유의해야 함)
[root@osmaster ~]# for i in `seq -w 001 015`; do oc delete pv pv$i; done
[root@osmaster ~]# rm -rf /mnt/persistent-volumes/metrics

  • Openshift 재시작, Guest 및 관리자 계정(admin) 생성

[root@osmaster ~]# systemctl restart origin-master reboot

[root@osmaster ~]# htpasswd -b /etc/origin/master/htpasswd admin openshift

[root@osmaster ~]# oc login -u system:admin

[root@osmaster ~]# oadm policy add-cluster-role-to-user cluster-admin admin

[root@osmaster ~]# oc get all 



기능 테스트 단계


  • 웹브라우저를 통해 https://osmaster.test.local:8443 으로 접속하면 Origin 로그인 화면이 나타나고 guest 계정으로 로그인이 정상적인지 확인해 보자


이게 전부라면 얼마나 좋을까만, 기쁘게도 확인하고 알아둬야 할 내용들이 더 있다(실은 아주 더 많다). 참고로 OpenShift 를 익숙하게 사용하기 위해서는 OpenShift Console Web(GUI)를 통한 기본 사용법, 프로젝트의 구성 현황/상태 확인 방법과 함께 CLI(oc, oadm 등 명령. → 참고) 사용과 빌드/배포 자동화를 위한 yaml 파일 수정/편집과 OpenShift 의 Architecture에 대한 이해가 필요할 듯 싶다. 



  • OpenShift 사용과 활용에 앞서


여기서는 

  • 먼저, Googling으로 쉽게 접해볼 수 있는 오픈소스 프로젝트 2가지를 GibHub 를 통해 연동하여 환경 구축을 한 후, OpenShift로 개발/운영을 수행하는 과정(프로젝트 생성, GitHub연동, Build & Deploy, 테스트 과정)을 살펴본 뒤, 
  • 두 번 째로 Auto-scaling metric 설정을 통한 부하(cpu/memory)에 따른 자동 scale-up/down  을 테스트 해 보고 
  • 세 번 째로 persistent volume 이 없을 때(docker의 ephemeral 특성)와 있을 때(persistent 기능 확보) 각각에 대해 어떤 차이가 보이는지 확인해 본 다음, 위에서 생성한 NFS server를 통한 persistent volume을 공유 스토리지로 사용해 볼 것이다, 



4가지 대표적인 Use Case 경험하기


  • GibHub 를 사용한 개발과정 맛보기 #1 - Spring MVC Showcase(Java)
  • GibHub 를 사용한 개발과정 맛보기 #2 - WordPress Sample & MySQL(PHP)
  • Auto-Scaling 의 사용(Hawkular Metrics)
  • Persistent-Volume 의 사용



  • GibHub 를 사용한 개발과정 맛보기 #1 - Spring MVC Showcase(Java)


spring-mvc-showcase 프로젝트를 포크하고 URL을 복사해 둔다. pom.xml 파일이 있는 Maven 프로젝트(Clone or download 클릭 & URL 복사)


OpenShift 콘솔에 로그인(설치 직후에 생성한 guest 계정 정보 guest / openshift 로 로그인)


New Project 를 선택하고 새로운 프로젝트를 생성(Create)


프로젝트를 생성하면 자동적으로 Add to Project 과정이 실행되고 프로젝트에 추가할 컴포넌트를 선택할 수 있게 된다. 여기서는 Java 선택


OpenShift 는 Java 를 구동할 수 있는 컴포넌트로 WildFly(WAS인 Jboss의 Open 버전) Docker Container를 기본으로 제공한다(Select)


Resource 이름을 적당히 지정하고, 위에서 복사해 둔 GitHub 프로젝트의 URL을 복사(Create)


"Application created." 라는 메시지와 함께 Continue to overview 링크를 누르고 싶게 만드는 화면이 나타난다. 아래에 Command Line Tool인 oc(OpenShift Client) 의 간단한 사용법이 보이는데, 현재 GUI상에서 수행하는 모든 작업들은 oc 명령어로 가능하며, GUI방식보다 디테일한 작업을 위해서는 oc 명령어를 익혀두어야 할 때가 올 수도 있다(일단 클릭)


다음 단계인 Build 과정으로, Summary 탭 내에서 Application의 빌드가 수행되고 있다는 표시로 동적아이콘이 빙빙 돌아가고 있다(View Log 클릭) 


터미널 창에서 oc logs 를 수행한 결과와 동일한 로그 내용이 실시간으로 확인 가능하다. 'Status: ' 우측에 역시 동적아이콘이 빙빙 돌아가고 있으며, Build가 완료되면 'Complete' 로 바뀐다.


Build가 완료되었다. 콘솔 창을 가장 아래로 스크롤해 보면 "Push successful" 이라고 메시지가 나올 것이다. Build가 완료된 직후, OpenShift는 Build된 바이너리 파일들이 담긴 Docker Image를 새로 생성된 Pod에 밀어 넣고 Application 이 실행 가능한 상태로 Deploy를 완료한다


왼쪽 메인 아이콘 목록에서 'Overview' 를 클릭, 프로젝트내 리소스를 확인한다. 하단 직사각형 Box 부분이 하나의 Service 이며, 'sample-01-mvc' 라는 서비스 내에 pod가 1개 위치하고, 해당 컨테이너의 Build 이미지 정보가 왼쪽에 표시된다. 상단에 서비스에 접속할 수 있는 URL(서비스 Routes)이 자동으로 생성되어 있음을 볼수 있다(URL 클릭) 


마지막으로, 현재까지 빌드&배포된 Application의 정상 동작을 확인해 본다. 앞에서 URL을 클릭하면 새로운 탭에서 OpenShift 가 만들어준 Application이 동작하는 웹페이지가 나타난다. 주소 끝에 'spring-mvc-showcase/' 경로를 입력하고 엔터 


Java 개발자에게 친숙한 MVC Showcase 페이지가 성공적으로 수행되었다. 각 탭을 클릭해서 정상 작동하는지 테스트 해보자


     GitHub 프로젝트의 소스를 개발자가 수정하는 경우를 따라가 보자. 일반적으로는 개발자가  git client 로 프로그램을 수정하고, 최종적으로 Origin의 Master Branch 에 수정이 가해지겠지만, 여기서는 단순히 테스트를 위한 것이므로 GitHub에서 Master를 직접 수정하는 것으로 진행한다.  'home.jsp' 파일을 위와 같이 수정하자


수정된 내용을 commit


메인 아이콘을 통해 'Builds > Builds' 클릭


Build 리스트의 빌드명 클릭


'Start Build' 클릭, 아래에 Build 진행 상태가 표시되며, 나머지는 앞서 최초 빌드 진행 과정과 거의 동일하며, 앞서와 마찬가지로 'View Log' 를 통해 진행 상황을 확인할 수 있다. 최종 수행 결과에 대한 확인 과정은 위와 반복되는 내용이므로 생략한다



아래에 이어지는 나머지 3가지 Case는, 페이지 분량이 많은 관계로 3편으로 이어서 연재한다. 


  • GibHub 를 사용한 개발과정 맛보기 #2 - WordPress Sample & MySQL(PHP)
  • Auto-Scaling 의 사용(Hawkular Metrics)
  • Persistent-Volume 의 사용



[연관되는 글]

[OpenShift v3 #1] Origin all-in-one, CentOS 7 기반 단일서버 설치 사용법(1/3)

[OpenShift v3 #3] Origin 3-node, CentOS 7 기반 3노드 설치, 사용 방법(3/3)


- Barracuda -


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

Barracuda

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


OpenShift 를 설치 하기위한 구성은 싱글 서버(All-In-One)부터 10대 이상의 구성까지 다양하다. 이번 포스팅에서는 단일서버에 OpenShift 를 설치하는 과정을 기록하고 관리하도록 한다. OpenShift 는 v3로 넘어오면서 Kubernetes 의 Container, Storage, SDN 오케스트레이션을 완성도 있게 구현하여, 한층 단순하고 안정적으로 다룰 수 있게 되었다. 즉 기존의 Gear/Broker/Cartridges 의 생소한 개념들을 걷어내고 Docker/Kubernetes/Docker Images 개념으로 재정립하여 구조적으로 간단해지고, 널리 알려진 인프라 기술이 적용되었으며, 더 나은 UX를 제공하게 된 것이다. 


최근에 들어서 기존의 rpm 설치 방식을 간결하게 만든 dockerized 설치 방식이 일반화되고 있으며, 여기서 설치할 OpenShift는 v3 Origin 버전의 경우도, Dockerized 설치 방식으로 Ansible-playbook 을 통해  로컬서버에 호스팅되는 형식으로 구현되어 있다.


참고로 본 글은, OpenShift 설치와 사용법에 관련한 총 2개의 시리즈 중 첫 번째로, 다음 회에서는 1 Master 2 Node 설치 과정과 Kubernetes를 통한 Docker/Container, Storage, Network의 Orchestration과 Build/Deploy 에 대해 실제 사용 사례(Use Case)별 구현과 체험을 간략히 진행해 보는 것으로 예정하고 있다. 



  • OpenShift v3 Concepts



사전 설정(Pre-requisites)

  • 서버 구성(최소 사양) : 2 CPU, 8 G memory, 20G+20G(추가디스크) Disk
    - OS는 CentOS 7.3, Minimal Server 버전
    - 추가디스크는 Persistent Storage를 위한 NFS 용 스토리지로, /mnt/persistent-volumes 에 마운트
  •  내부 테스트 또는 외부 서비스용 별도 DNS 서버 필요
    - 여기서는 내부 테스트용 test.local 도메인을 named 를 통해서 존파일 설정(DNS forwarding 사용)



  • DNS forwarding 을 위한 설정 내용을 일단 정리해두고 넘어가자(192.168.0.0/16 네트워크 사용시).


[root@dns ~]# cat /etc/named.conf 

options {

listen-on port 53 { any; };

directory "/var/named";

dump-file "/var/named/data/cache_dump.db";

statistics-file "/var/named/data/named_stats.txt";

memstatistics-file "/var/named/data/named_mem_stats.txt";


allow-query     { any; };

recursion yes;


// For non-internal domains

forwarders {

168.126.63.1;

8.8.8.8;

};

auth-nxdomain no; # conform to RFC1035

dnssec-enable yes;

// dnssec-validation yes;

dnssec-lookaside auto;


/* Path to ISC DLV key */

bindkeys-file "/etc/named.iscdlv.key";


managed-keys-directory "/var/named/dynamic";


pid-file "/run/named/named.pid";

session-keyfile "/run/named/session.key";

};


logging {

        channel default_debug {

                file "data/named.run";

                severity dynamic;

        };

};


zone "." IN {

type hint;

file "named.ca";

};


include "/etc/named.rfc1912.zones";

include "/etc/named.root.key";


[root@dns ~]# cat /etc/named.rfc1912.zones

...(생략)...

zone "0.in-addr.arpa" IN {

type master;

file "named.empty";

allow-update { none; };

};


zone "test.local" IN {

        type master; // server type

        file "test.local.zone"; // zone file

        allow-update {none;}; // none: no slave to sync

};


zone "168.192.in-addr.arpa" IN { // Reverse

        type master;

        file "/var/named/rev.168.192.in-addr.arpa";

};


[root@dns ~]# cat /var/named/test.local.zone

$ORIGIN .

$TTL 10


test.local.    IN    SOA    dns.test.local.    root.test.local. (

2017031402    ; serial

1D       ; refresh

1H       ; retry

1W      ; expire

3H )    ; min(Negative cache TTL)

test.local.          IN    NS    dns.test.local.

dns.test.local.    IN    A    192.168.0.3


$ORIGIN    test.local.


openshift3    IN    A    192.168.10.145

*.openshift3  IN    A    192.168.10.145


[root@dns ~]# cat /var/named/rev.168.192.in-addr.arpa 

$TTL 10


@    IN    SOA    dns.test.local.    root.test.local. (

2017031403 ; serial

1D ; refresh

1H ; retry

1W ; expire

3H ) ; min(Negative cache TTL)

test.local.          IN    NS    dns.test.local.

dns.test.local.    IN    A    192.168.0.3


@      IN NS      dns.test.local.

3.0    IN PTR    dns.test.local.


145.10    IN PTR    openshift3.test.local.



  • 준비된 노드에서 다음과 같은 사전설치 과정을 따른다

# vi /etc/sysconfig/network-scripts/ifcfg-eth0

...

NETMASK=255.255.0.0

DNS1=192.168.0.3

DNS2=8.8.8.8

# hostnamectl set-hostname openshift14-allinone

# yum install -y bind-utils git iptables-services

# systemctl enable iptables

# systemctl start iptables

# nslookup openshift3.test.local

# dig yahoo.com a


# ssh-keygen

# ssh-copy-id root@openshift3.test.local

# ssh root@openshift3.test.local



OpenShift v3 설치 시작


  • EPEL repo를 설정하고 필요한 패키지 설치

# yum install -y epel-release

# yum install -y docker wget ansible 

# yum install -y python-cryptography pyOpenSSL.x86_64


  • OpenShift Ansible 패키지와 configuration 소스 설치, 설정

# git clone https://github.com/DragOnMe/openshift-ansible.git

# git clone https://github.com/DragOnMe/playbook-allinone-cfg.git

# vi playbook-allinone-cfg/centos7-1node-playbook.cfg

[OSEv3:children]

masters

nodes


[OSEv3:vars]

ansible_ssh_user=root

deployment_type=origin

openshift_master_identity_providers=[{'name': 'htpasswd_auth', 'login': 'true', 'challenge': 'true', 'kind': 'HTPasswdPasswordIdentityProvider', 'filename': '/etc/origin/master/htpasswd'}]

containerized=true

openshift_release=v1.4.1

openshift_image_tag=v1.4.1

openshift_public_hostname=openshift3.test.local

openshift_master_default_subdomain=apps.openshift3.test.local

openshift_hosted_metrics_deploy=false



[masters]

openshift3.test.local openshift_schedulable=true


[nodes]

openshift3.test.local openshift_node_labels="{'region': 'infra', 'zone': 'default'}" openshift_schedulable=true


# ansible-playbook -i ./playbook-allinone-cfg/centos7-1node-playbook.cfg ./openshift-ansible/playbooks/byo/config.yml


  • 실행 도중 오류가 나는 경우 진행되었던 설치 과정의 흔적을 Clear 한 후, 각종 문제(주로 selinux 설정, firewalld-iptables 관련 문제 등)을 해결하고 다시 설치를 시도해야 한다

# ansible-playbook -i ./playbook-allinone-cfg/centos7-1node-playbook.cfg ./openshift-ansible/playbooks/adhoc/uninstall.yml


  • 최초 OpenShift 계정으로 guest 계정 생성, 접속 테스트

# cd /etc/origin/master/

# cat htpasswd

# htpasswd -b /etc/origin/master/htpasswd guest openshift

# oc login

# oc status

# oc logout


  • NFS를 persistent storage 로 설치하기 위해 nfs-server 설치, 설정

# yum groupinstall -y file-server

# systemctl enable rpcbind

# systemctl enable nfs-server

# systemctl start rpcbind

# systemctl start nfs-server

# iptables-save > pre-nfs-firewall-rules-server

# iptables -I INPUT -m state --state NEW -p tcp -m multiport --dport 111,892,2049,32803 -s 0.0.0.0/0 -j ACCEPT

# iptables -I INPUT -m state --state NEW -p udp -m multiport --dport 111,892,2049,32769 -s 0.0.0.0/0 -j ACCEPT

# service iptables save

# setsebool -P virt_use_nfs 1


  • OpenShift용 Persistent Volume 생성(1GB * 15)

# vi playbook-allinone-cfg/pv.json

{

 "apiVersion": "v1",

 "kind": "PersistentVolume",

 "metadata": {

 "name": "pv001"

},

"spec": {

   "capacity": {

   "storage": "1Gi"

   },

   "accessModes": [ "ReadWriteOnce" ],

   "nfs": {

      "path": "/mnt/persistent-volumes/pv0001",

      "server": "192.168.10.145"

   },

   "persistentVolumeReclaimPolicy": "Recycle"

   }

}


# oc login -u system:admin

# for i in `seq -w 001 015`; do \

SHARE=/mnt/persistent-volumes/pv$i; \

mkdir -p $SHARE; chmod 777 $SHARE; \

chown nfsnobody:nfsnobody $SHARE; \

echo "$SHARE 192.168.0.0/16(rw,all_squash)" >>/etc/exports; \

sed s/pv001/pv$i/g /root/playbook-allinone-cfg/pv.json | oc create -f -; \

done

# oc get pv

# oc version

# oc get user

# oc config view

# oc get all


  • Guest 및 관리자 계정(admin) 생성

# htpasswd -b /etc/origin/master/htpasswd admin openshift

# oc login -u system:admin

# oadm policy add-cluster-role-to-user cluster-admin admin



마무리 & 테스트 단계


  • 웹브라우저를 통해 https://openshift3.test.local:8443 으로 접속하면 Origin 로그인 화면이 나타난다




다음 회를 기약하며


이번 회에서는 단일 서버 구성의 설치 과정에 대해서만 간단히 정리하고, 다음 #2 회에서 3-노드 설치, 설정과 함께, 3가지의 OpenShift 사용 Use Case를 직접 다뤄 보면서 조금 더 사용법과 개념에 익숙해 지는 기회를 만들고자 한다.


참고로 OpenShift Origin을 VirtualBox 등의 단일 VM내에서 작동시킬 수 있는 MiniShift 도 있으니 한 번 설치, 사용해 볼 만하다. DNS를 통하지 않는 IP 주소 기반의 사용 방식이지만 OpenShift의 대다수 기능들을 그대로 사용할 수 있게 만들어져 있다. 참조 주소는 다음과 같다.




[연관되는 글]

[OpenShift v3 #2] Origin 3-node, CentOS 7 기반 3노드 설치, 사용 방법(2/3)

[OpenShift v3 #3] Origin 3-node, CentOS 7 기반 3노드 설치, 사용 방법(3/3)



- Barracuda -



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

Barracuda

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


Openstack Mitaka 최종본(v6) 설치 및 설정 종합본



본 편의 도입 및 준비 부분은, 위의 지난 2016년 9월에 #1 편에서 다뤘던 Mitaka v5 버전 내용을 그 근간으로 한다. Mitaka v5 는 지난 2016년 6월에 출시된 이후 거의 몇 일 ~ 몇 주 단위로 활발하게 수정 변경되어 11월 9일에 최종 버전인 v6 가 릴리즈되어 있다. 참고로 CentOS 계열(Redhat, CentOS, Fedora)을 위한 디플로이 도구인 Packstack 의 Mitaka 를 위한 버전은 다음 그림에서 보다시피 v5, v6 만 존재하고 있다.



지난 9월의 계획에서는 2016년 10월 릴리즈 예정이었던 Newton 버전을 설치하고 검증하는 과정을 다루려고 했으나, 릴리즈 직후에 한 번 시험삼아 설치해 보니 지나치게 불안정한 면이 커서, 현 시점의 안정적인 Mitaka 최종 버전을 따로 정리해 두는 편이 나을 듯 하여 급하게 본 포스팅을 구성해 보려 한다.


  • 설치 준비 과정 이후의 상세 내용


전 편의 1~5 섹션까지 내용은 그대로 사용하는 것으로 하고, 이후의 섹션 6부터 전 편의 보충 형식으로 써내려 간다. 결론적으로는, 오픈스택 Mitaka 버전을 설치, 설정하기 위해서는 지난 #1편(1~5섹션)과 본 #2편(6섹션 이후)를 연이어서 참고하면 되지 않을까 하는 생각이다.



6. 오픈스택 설치를 위한 각 노드들의 패키지 설정


  • 각 노드의 리포지토리를 변경하고 필수 패키지 설정


이미 위에서 언급했다시피, 다수의 의존성 패키지를 따로 설치하지 않고 yum update -y 를 통해 최신 패키지 업데이트를 진행한 다음 Openstack v6 를 따로 다운로드 설치하는 과정을 아래에 써내려 간다. 역시 Mitaka의 최종 버전이니만큼 비교적 안정적이고 오류 없이 잘 진행됨을 확인하였다(9월버전 v5에서 오류가 발생하였던 lbaas 포함 설치도 v6에서는 오류 없이 잘 진행됨).


# yum update -y

# yum install -y net-tools

# yum install -y wget


  • 각 노드의 /etc/hosts 파일에 다음의 3개 라인을 추가

10.10.10.6 openstack-ctrl

10.10.10.7 openstack-net

10.10.10.8 openstack-cmp1


  • 마찬가지로 각 3개 노드에서 보안 등 각종 관리패키지를 오픈스택 설치가 가능해 지도록 구성

# systemctl disable firewalld

# systemctl stop firewalld

# systemctl disable NetworkManager

# systemctl stop NetworkManager

# yum remove -y NetworkManager

# systemctl enable network

# systemctl start network



7. Packstack 으로 Openstack 자동 설치하기

Packstack은 Controller 노드에서 answer 파일을 읽어 들여서 자신을 포함하여 하위 노드(Network 노드, Compute 노드들)에서 각종 소프트웨어들을 설치/설정하는 스크립트를 단계적으로 실행해 주는 역할을 한다(Redhat 제공 RDO 프로젝트 참고: www.rdoproject.org). 이를 위해 각 하위 노드에 암호 없이 접속이 가능해야 한다. 


  • Controller 노드에서 각 노드로 Passwordless ssh 설정

[root@openstack-ctrl ~]# ssh-keygen 

[root@openstack-ctrl ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.10.10.7

[root@openstack-ctrl ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.10.10.8


#--> 암호 없이 정상적으로 접속되는지 확인

[root@openstack-ctrl ~]# ssh openstack-net

[root@openstack-ctrl ~]# ssh openstack-cmp1


  • Packstack repo 설정 및 설치 시작
이전에 진행했던 방식으로 다음과 같이 rdo-release.rpm 을 다운로드하고 설치하면, 현재 시점의 가장 최근 버전인 newton 버전이 설치되어 버리므로,

[root@openstack-ctrl ~]# yum install -y https://www.rdoproject.org/repos/rdo-release.rpm

[root@openstack-ctrl ~]# yum install -y openstack-packstack

...


특정 버전의 Openstack을 명시적으로 다운로드 & 설치하기 위해서는 https://repos.fedorapeople.org/repos/openstack/ 에서 해당 버전을 찾아서 설치하는 과정을 밟아 가면 된다. 여기서는 생성된 answer.txt 파일 항목 중 특별히 편집 수정해야 하는 부분을 표시하여 간략한 설명을 달아 두도록 한다.

[root@openstack-ctrl ~]# yum install -y https://repos.fedorapeople.org/repos/openstack/openstack-mitaka/rdo-release-mitaka-6.noarch.rpm

[root@openstack-ctrl ~]# yum update -y

[root@openstack-ctrl ~]# yum install -y openstack-packstack

[root@openstack-ctrl ~]# su - stack

[stack@openstack-ctrl ~]# sudo packstack --gen-answer-file=answer.txt

[stack@openstack-ctrl ~]# sudo vi answer.txt

[general]

...

# 개별 항목에 자동 적용 되는 공통 암호

CONFIG_DEFAULT_PASSWORD=openstack


# 여기서는 사용하지 않음

CONFIG_MANILA_INSTALL=n


# 여기서는 사용하지 않음

CONFIG_SWIFT_INSTALL=n


# 미터링 패키지 설치

CONFIG_CEILOMETER_INSTALL=y


# 여기서는 사용하지 않음

CONFIG_AODH_INSTALL=n


# 여기서는 사용하지 않음

CONFIG_GNOCCHI_INSTALL=n


# 여기서는 사용하지 않음

CONFIG_SAHARA_INSTALL=n


# 오케스트레이션을 위한 패키지 설치

CONFIG_HEAT_INSTALL=y


# 여기서는 사용하지 않음

CONFIG_IRONIC_INSTALL=n


# ntp 시간동기화는 수동 설치 및 설정

CONFIG_NTP_SERVERS=


# 여기서는 사용하지 않음

CONFIG_NAGIOS_INSTALL=n


# API 서비스 패키지 등이 설치되는 Controller 노드

CONFIG_CONTROLLER_HOST=10.10.10.6


# Compute 서비스 패키지들이 설치되는 노드

CONFIG_COMPUTE_HOSTS=10.10.10.8


# Compute networking (nova network) 또는 OpenStack Networking (neutron) 이 설치 되는 Network 노드

CONFIG_NETWORK_HOSTS=10.10.10.7


# AMQP 서비스 설치 노드(여기서는 Controller 노드)

CONFIG_AMQP_HOST=10.10.10.6


# 기본 DB인 MariaDB 설치 노드(여기서는 Controller 노드)

CONFIG_MARIADB_HOST=10.10.10.6


# MariaDB 관리자 계정 암호

CONFIG_MARIADB_PW=openstack


# Keystone DB 암호

CONFIG_KEYSTONE_DB_PW=openstack


# Identity 서비스 admin 계정 암호

CONFIG_KEYSTONE_ADMIN_PW=openstack


# Identity service demo 계정 암호

CONFIG_KEYSTONE_DEMO_PW=openstack


# Glance DB 암호

CONFIG_GLANCE_DB_PW=openstack


# Glance Identity service 암호

CONFIG_GLANCE_KS_PW=openstack


# Nova DB 암호

CONFIG_NOVA_DB_PW=openstack


# Nova Identity service 암호

CONFIG_NOVA_KS_PW=openstack


# Neutron Identity service 암호

CONFIG_NEUTRON_KS_PW=openstack


# Neutron DB 암호

CONFIG_NEUTRON_DB_PW=openstack


# Neutron L3 의 External OVS bridge 지정(비워 두면 default로 linuxbridge)

CONFIG_NEUTRON_L3_EXT_BRIDGE=br-ex


# OpenStack Networking metadata agent 암호.

CONFIG_NEUTRON_METADATA_PW=openstack


# OpenStack Networking's Load-Balancing-as-a-Service (LBaaS) 설치 여부

CONFIG_LBAAS_INSTALL=y


# OpenStack Networking's L3 Metering agent 설치 여부

CONFIG_NEUTRON_METERING_AGENT_INSTALL=y


# neutron.ml2.type_drivers 로 사용할 드라이버 지정. comma 로 구분 ['local','flat', 'vlan', 'gre', 'vxlan']

CONFIG_NEUTRON_ML2_TYPE_DRIVERS=vxlan


# Tenant network로 사용할 network type을 순서대로 지정. comma 로 구분 ['local', 'vlan', 'gre', 'vxlan']

CONFIG_NEUTRON_ML2_TENANT_NETWORK_TYPES=vxlan


# neutron.ml2.mechanism_drivers 로 로딩할 메커니즘 드라이버 지정. comma 로 구분

# ['logger', 'test', 'linuxbridge', 'openvswitch', 'hyperv', 'ncs', 'arista', 'cisco_nexus', 'mlnx', 'l2population', 'sriovnicswitch']

CONFIG_NEUTRON_ML2_MECHANISM_DRIVERS=openvswitch


# OpenStack Networking으로 사용할 L2 agent 지정 ['linuxbridge', 'openvswitch']

CONFIG_NEUTRON_L2_AGENT=openvswitch


# HEAT DB 암호

CONFIG_HEAT_DB_PW=openstack


# HEAT Identity service 암호

CONFIG_HEAT_KS_PW=openstack


# HEAT Identity domain 관리자 암호

CONFIG_HEAT_DOMAIN_PASSWORD=openstack


# Provisioning 테스트를 위한 demo 설치 여부

CONFIG_PROVISION_DEMO=y


# Ceilometer Identity service 암호

CONFIG_CEILOMETER_KS_PW=openstack


# MongoDB 설치 서버(여기서는 Controller 노드)

CONFIG_MONGODB_HOST=10.10.10.6


# Redis master server

CONFIG_REDIS_MASTER_HOST=10.10.10.6


[stack@openstack-ctrl ~]# sudo packstack --answer-file=answer.txt

Welcome to the Packstack setup utility


The installation log file is available at: /var/tmp/packstack/20161209-052810-KZtyhp/openstack-setup.log


Installing:

Clean Up                                             [ DONE ]

Discovering ip protocol version                      [ DONE ]

root@10.10.10.6's password: 

Setting up ssh keys                                  [ DONE ]

Preparing servers                                    [ DONE ]

Pre installing Puppet and discovering hosts' details [ DONE ]

Adding pre install manifest entries                  [ DONE ]

Setting up CACERT                                    [ DONE ]

Adding AMQP manifest entries                         [ DONE ]

Adding MariaDB manifest entries                      [ DONE ]

Adding Apache manifest entries                       [ DONE ]

Fixing Keystone LDAP config parameters to be undef if empty[ DONE ]

Adding Keystone manifest entries                     [ DONE ]

Adding Glance Keystone manifest entries              [ DONE ]

Adding Glance manifest entries                       [ DONE ]

Adding Nova API manifest entries                     [ DONE ]

Adding Nova Keystone manifest entries                [ DONE ]

Adding Nova Cert manifest entries                    [ DONE ]

Adding Nova Conductor manifest entries               [ DONE ]

Creating ssh keys for Nova migration                 [ DONE ]

Gathering ssh host keys for Nova migration           [ DONE ]

Adding Nova Compute manifest entries                 [ DONE ]

Adding Nova Scheduler manifest entries               [ DONE ]

Adding Nova VNC Proxy manifest entries               [ DONE ]

Adding OpenStack Network-related Nova manifest entries[ DONE ]

Adding Nova Common manifest entries                  [ DONE ]

Adding Neutron VPNaaS Agent manifest entries         [ DONE ]

Adding Neutron FWaaS Agent manifest entries          [ DONE ]

Adding Neutron LBaaS Agent manifest entries          [ DONE ]

Adding Neutron API manifest entries                  [ DONE ]

Adding Neutron Keystone manifest entries             [ DONE ]

Adding Neutron L3 manifest entries                   [ DONE ]

Adding Neutron L2 Agent manifest entries             [ DONE ]

Adding Neutron DHCP Agent manifest entries           [ DONE ]

Adding Neutron Metering Agent manifest entries       [ DONE ]

Adding Neutron Metadata Agent manifest entries       [ DONE ]

Adding Neutron SR-IOV Switch Agent manifest entries  [ DONE ]

Checking if NetworkManager is enabled and running    [ DONE ]

Adding OpenStack Client manifest entries             [ DONE ]

Adding Horizon manifest entries                      [ DONE ]

Adding Heat manifest entries                         [ DONE ]

Adding Provisioning manifest entries                 [ DONE ]

Adding Provisioning Glance manifest entries          [ DONE ]

Adding Provisioning Demo bridge manifest entries     [ DONE ]

Adding MongoDB manifest entries                      [ DONE ]

Adding Redis manifest entries                        [ DONE ]

Adding Ceilometer manifest entries                   [ DONE ]

Adding Ceilometer Keystone manifest entries          [ DONE ]

Copying Puppet modules and manifests                 [ DONE ]

Applying 10.10.10.8_prescript.pp

Applying 10.10.10.7_prescript.pp

Applying 10.10.10.6_prescript.pp

10.10.10.7_prescript.pp:                             [ DONE ]      

10.10.10.8_prescript.pp:                             [ DONE ]      

10.10.10.6_prescript.pp:                             [ DONE ]      

Applying 10.10.10.6_amqp.pp

Applying 10.10.10.6_mariadb.pp

10.10.10.6_amqp.pp:                                  [ DONE ]    

10.10.10.6_mariadb.pp:                               [ DONE ]    

Applying 10.10.10.6_apache.pp

10.10.10.6_apache.pp:                                [ DONE ]   

Applying 10.10.10.6_keystone.pp

Applying 10.10.10.6_glance.pp

10.10.10.6_keystone.pp:                              [ DONE ]     

10.10.10.6_glance.pp:                                [ DONE ]     

Applying 10.10.10.6_api_nova.pp

10.10.10.6_api_nova.pp:                              [ DONE ]     

Applying 10.10.10.6_nova.pp

Applying 10.10.10.8_nova.pp

Applying 10.10.10.7_neutron.pp

10.10.10.6_nova.pp:                                  [ DONE ]    

10.10.10.7_neutron.pp:                               [ DONE ]    

10.10.10.8_nova.pp:                                  [ DONE ]    

Applying 10.10.10.8_neutron.pp

Applying 10.10.10.6_neutron.pp

10.10.10.8_neutron.pp:                               [ DONE ]    

10.10.10.6_neutron.pp:                               [ DONE ]    

Applying 10.10.10.6_osclient.pp

Applying 10.10.10.6_horizon.pp

10.10.10.6_osclient.pp:                              [ DONE ]     

10.10.10.6_horizon.pp:                               [ DONE ]     

Applying 10.10.10.6_heat.pp

10.10.10.6_heat.pp:                                  [ DONE ] 

Applying 10.10.10.6_provision.pp

Applying 10.10.10.6_provision_glance

10.10.10.6_provision.pp:                             [ DONE ]          

10.10.10.6_provision_glance:                         [ DONE ]          

Applying 10.10.10.7_provision_bridge.pp

10.10.10.7_provision_bridge.pp:                      [ DONE ]             

Applying 10.10.10.6_mongodb.pp

Applying 10.10.10.6_redis.pp

10.10.10.6_mongodb.pp:                               [ DONE ]    

10.10.10.6_redis.pp:                                 [ DONE ]    

Applying 10.10.10.6_ceilometer.pp

10.10.10.6_ceilometer.pp:                            [ DONE ]       

Applying Puppet manifests                            [ DONE ]

Finalizing                                           [ DONE ]


 **** Installation completed successfully ******


Additional information:

 * Time synchronization installation was skipped. Please note that unsynchronized time on server instances might be problem for some OpenStack components.

 * File /root/keystonerc_admin has been created on OpenStack client host 10.10.10.6. To use the command line tools you need to source the file.

 * To access the OpenStack Dashboard browse to http://10.10.10.6/dashboard .

Please, find your login credentials stored in the keystonerc_admin in your home directory.

 * Because of the kernel update the host 10.10.10.8 requires reboot.

 * Because of the kernel update the host 10.10.10.7 requires reboot.

 * Because of the kernel update the host 10.10.10.6 requires reboot.

 * The installation log file is available at: /var/tmp/packstack/20161209-052810-KZtyhp/openstack-setup.log

 * The generated manifests are available at: /var/tmp/packstack/20161209-052810-KZtyhp/manifests



8. Network 노드 - Neutron 네트워크(OVS bridge) 인터페이스 설정 확인


#1 편에서 수동으로 설정해 주어야 했던 Network 노드의 외부 bridge 인터페이스(br-ex)와 enp0s9 의 브리지 포트 연결이, Mitaka v6에서는 자동으로 이루어짐을 볼 수 있다. 다음의 결과를 확인해 보자 


  • Network 노드 브리지 인터페이스 설정 확인

[root@openstack-net ~]# ifconfig

br-ex: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500

        inet 192.168.0.7  netmask 255.255.255.0  broadcast 192.168.0.255

        inet6 fe80::8464:32ff:fe27:304f  prefixlen 64  scopeid 0x20<link>

        ether 08:00:27:bd:a3:bd  txqueuelen 0  (Ethernet)

        RX packets 138  bytes 21542 (21.0 KiB)

        RX errors 0  dropped 0  overruns 0  frame 0

        TX packets 59  bytes 6843 (6.6 KiB)

        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0


enp0s9: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500

        inet6 fe80::a00:27ff:febd:a3bd  prefixlen 64  scopeid 0x20<link>

        ether 08:00:27:bd:a3:bd  txqueuelen 1000  (Ethernet)

        RX packets 412  bytes 84303 (82.3 KiB)

        RX errors 0  dropped 0  overruns 0  frame 0

        TX packets 71  bytes 9555 (9.3 KiB)

        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0


...



이제 ifcfg-br-ex, ifcfg-enp0s9 인터페이스도 확인해 보자

[root@openstack-net ~]# cat /etc/sysconfig/network-scripts/ifcfg-br-ex

DEVICE=br-ex

DEVICETYPE=ovs

TYPE=OVSBridge

BOOTPROTO=static

IPADDR=192.168.0.7

NETMASK=255.255.255.0

BROADCAST=192.168.0.255

GATEWAY=192.168.0.1

DEFROUTE=yes

ONBOOT=yes

NM_CONTROLLED=no

IPV4_FAILURE_FATAL=yes

IPV6INIT=no


[root@openstack-net ~]# cat /etc/sysconfig/network-scripts/ifcfg-enp0s9

DEVICE=enp0s9

DEVICETYPE=ovs

TYPE=OVSPort

OVS_BRIDGE=br-ex

ONBOOT=yes

NM_CONTROLLED=no

IPV6INIT=no



[root@openstack-net ~]# ovs-vsctl show

fd89eff3-c539-4250-8922-854f550124bc

    Bridge br-ex

        Port br-ex

            Interface br-ex

                type: internal

        Port "qg-05c23c0b-11"

            Interface "qg-05c23c0b-11"

                type: internal

        Port "enp0s9"

            Interface "enp0s9"

    Bridge br-int

        fail_mode: secure

        Port patch-tun

            Interface patch-tun

                type: patch

                options: {peer=patch-int}

        Port "qr-02fc6bc6-04"

            tag: 1

            Interface "qr-02fc6bc6-04"

                type: internal

        Port br-int

            Interface br-int

                type: internal

        Port "tap44e6cf82-31"

            tag: 1

            Interface "tap44e6cf82-31"

                type: internal

    Bridge br-tun

        fail_mode: secure

        Port "vxlan-0a0a0a08"

            Interface "vxlan-0a0a0a08"

                type: vxlan

                options: {df_default="true", in_key=flow, local_ip="10.10.10.7", out_key=flow, remote_ip="10.10.10.8"}

        Port patch-int

            Interface patch-int

                type: patch

                options: {peer=patch-tun}

        Port br-tun

            Interface br-tun

                type: internal

    ovs_version: "2.5.0"

Open vSwitch 의 브리지 인터페이스를 조회해 보면, br-ex 브리지에 br-ex 포트와 enp0s9 포트가 연결되어 있음을 볼 수 있다.



Controller 노드의 환경변수 설정 스크립트 복사


  • Network 노드, Compute 노드에서 CLI 명령 실행이 필요할 경우에 대비하여 스크립트 복사


Controller 노드의 keystonerc 관련 파일들을 다음과 같이 Network 노드, Compute 노드에 복사해 둔다


[root@openstack-ctrl ~]# scp keystonerc_* root@openstack-net:~

[root@openstack-ctrl ~]# scp keystonerc_* root@openstack-cmp1:~



9. Horizon 웹을 통해 Cloud 플랫폼 사용 준비 과정


  • Horizon 웹 UI 사용 준비 - 클라우드 네트워크 설정


시스템 구성도의 Admin VM에 해당하는 VM에서, firefox 웹브라우저를 통해 http://10.10.10.6으로 접속, admin 계정과 answer.txt 에서 지정한 암호를 통해 로그인


tenant 서비스를 위해서 demo 프로젝트를 사용한다고 가정했을 때,  해당 프로젝트에서 사용할 네트워크를 생성, 설정하는 과정을 다음과 같이 진행한다.


System->Routers 페인, 라우터 목록에서 router1 삭제 


System->Networks 페인, 사용하지 않는 네트워크(public_subnet) 을 삭제



networks 패널에서, '+Create Network' 을 선택하고 admin 프로젝트에 대해 public 네트워크를 새로이 생성(본 케이스에서는 vxlan 방식, shared - External network).



Subnet Detail 에서는, 네트워크 내부에 DHCP 서버(iptime 공유기)가 존재하므로, Enable DHCP 는 해제하고, 할당 가능한 IP 범위를 입력


admin 계정을 로그아웃하고, tenant 서비스를 설정/사용하기 위한 demo 계정으로 로그인


앞에서 admin 을 통해 생성, 설정한 네트워크를 사용하기 위해, 왼쪽 메뉴에서 Project->Network->Router 패널을 선택하고 '+Create Router' 을 클릭하여 demo 프로젝트에서 사용할 라우터를 생성. External Network 항목에 대해, 앞서 생성했던 public 네트워크를 선택





'+Add Interface' 클릭을 통해, 앞서 생성한 사설 네트워크와의 연결을 위한 인터페이스를 추가



좌측 메뉴의 Network 패널에서 Network Topology 를 선택하여 현재 구성된 네트워크의 구조를 확인한다. 여기까지 성공적으로 진행하였다면 클라우드 환경을 본격적으로 사용하기 위한 전체 설정 과정은 완료한 것이며, 다음의 필수 준비과정 몇 가지를 거치면 모든 준비가 끝나고 VM을 생성, 관리할 수 있는 요건을 갖추게 되었다.


11. VM 인스턴스 생성 관리를 위한 준비 & 실행 단계


  • security Group 생성 확인

 Compute->Access & Security->Security Group


시스템에 디폴트로 적용되는 Security Group 이 이미 생성되어 있다. 필요시 별도로 생성하여 사용하면 됨.


  • Key Pair 생성



Compute->Access & Security->Key Pairs->Create Key Pair, pem 파일 다운로드 및 보관



  • Floating IP 생성



Floating IP를 할당 필요한 갯수만큼 할당 받는다



  • VM Image 확인


Openstack 에 포함된 테스트용 초소형 OS image(12MB) 외에도, Ubuntu Cloud image 사이트나 CentOS cloud image 사이트 등에서 다운로드 받아 두고, Openstack image로 업로드(admin 계정으로)해서 사용할 수 있다


  • VM instance 생성








  • Ubuntu VM instance 생성하고 접속하기

별도의 전용 Security Group 을 생성해 둔다


Ubuntu VM을 만들기 위한 Flavor 템플릿을 생성해 둔다(admin 계정)








새로이 Ubuntu VM Instance 를 생성하고, 앞에서 할당해 두었던 Floating IP 들 중에서 하나를 선택하여 Associate 를 클릭하면 VM의 내부 사설 port(10.0.0.*) 과 Floating IP가 연결된다



Ubuntu VM 에 연결된 Floating IP 로 ping 테스트와 ssh 접속을 수행해 본다



12. LBaas(LB As A Service) 의 설정과 테스트


  • Haproxy Load Balancer 사용을 위한 준비

새로운 VM과 LB에 할당할 Floating IP 를 추가한다


LB pool 에 할당할 새로운 Ubuntu VM 을 생성해 둔다




LB pool 에 포함될 VM 들에 접속하여 nginx 패키지를 설치한다(외부 접속을 위해 /etc/resolv.conf 에 nameserver 를 추가하고, apt-get update 를 한 이후, apt-get install nginx 로 패키지 설치). 설치 이후 curl http://localhost 로 자체 접속 테스트.


각 VM의 웹서버에 80 port 로 접속이 가능하도록, 기존의 Security Group에 새로운 Rule 을 추가한다


VM들의 웹서버에 접속이 가능한지 웹브라우저를 통해 각각 접속해 본다



Network->Load Balancer, +Add Pool 클릭 및 저장 후,


Add VIP 선택


IP address에는 VM의 사설 IP 대역에 해당하는 IP를 입력. Connection Limit 는 -1 또는 제한되는 연결 갯수 입력 & 저장


Associate Floating IP 선택.


미리 준비한 Floating IP를 선택하고 Associate 클릭 & 저장


Members 탭 선택, +Add Member 클릭


Add Member 클릭. Pool 선택 & 대상 VM, port 번호 등 설정



웹브라우저에서 LB VIP로 접속하여 확인 완료



- Barracuda -


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

Barracuda

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

Gluster 파일시스템의 Geo-replication 환경을 구축하고 테스트 하는 과정을 정리해 보자. 작업 환경을 단순하게, 과정을 직관적으로 표현하기 위해 추가 디스크 없는 VM 2개만으로 모든 내용을 소화해 보기로 한다. 이를 위해 Linux 의 Sparse 파일을 가상 디스크로 매핑하여, 마치 추가 디스크가 장착된, 또는 별도 파티션 된 물리 디스크 볼륨이 있는 것처럼 흉내 내는 방법을 같이 다루어 보고자 한다(블록디바이스를 통한 비슷한 테스트를 진행해야 할 때 유용한 방법으로 써먹을 수 있을 것이다).



CentOS 7.2에 Gluster Filesystem 설치


먼저 위의 그림과 같이 네트워크가 연결된 2개의 가상머신을 준비한다(CentOS 7.2 또는 RHEL 7.2는 이미 설치되었다고 가정하자). Redhat 이나 CentOS의 경우 대개 EPEL 을 통해 Gluster 를 설치하게 된다.


* 주의: 2개의 서버 노드를 peer 로 연결하여 Cluster 에 투입하는 것은 아니며, 서로 독립된 Gluster 세트가 원격지에 각각 존재하는 경우에 대한 내용을 다룬다. 여러 peer 를 통하여 Brick을 구성하고 데이터가 분산(distributed) 또는 중복(replica)되는 Volume 구성에 대해서는 여기서 논외로 하자.


* 2개의 서버 각각에 다음과 같이 gluster 를 설치한다.

# yum install -y wget

# wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-8.noarch.rpm

# rpm -ivh epel-release-7-8.noarch.rpm

# yum install -y centos-release-gluster38.noarch

# yum install -y glusterfs-server

yum install -y glusterfs-geo-replication

# systemctl start glusterd

# systemctl enable glusterd


* RHEL 7 에서는 약간 과정이 다르다. 다음과 같이 해 보자.

# yum install -y wget

# wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-8.noarch.rpm

# rpm -ivh epel-release-7-8.noarch.rpm

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

[gluster38]

name=Gluster 3.8

baseurl=http://mirror.centos.org/centos/7/storage/$basearch/gluster-3.8/

gpgcheck=0

enabled=1

# yum install -y glusterfs-server

# yum install -y glusterfs-geo-replication

# systemctl start glusterd

# systemctl enable glusterd


* 커맨드를 단순하게 하기 위해 /etc/hosts 파일에 다음의 2 라인을 추가

10.10.10.10 gnode1

10.10.10.11 gnode2


* 호스트 명을 각각 gnode1, gnode 로 변경하고 재접속

hostnamectl set-hostname gnode1



준비1: 데이터를 동기화할 가상 블록디바이스 장착

서두에서 말한 것처럼 Sparse 파일(1 GByte 짜리 ^^;;)을 만들어 가상디스크로 붙이고, 이를 데이터 동기화용 블록디바이스로 활용할 것이다. VM 2개에 각각 다음과 같이 작업한다.


* losetup -f 로 Sparse 이미지 파일을 LO 디바이스에 매핑하면, 자동으로 다음의 빈 LO 디바이스(아래의 경우, /dev/loop2)가 찾아져서 매핑됨

* 블록디바이스가 준비되었으므로 xfs 로 파일시스템을 포맷하고 마운트포인트를 /mnt/gnode_disk1 디렉토리로 하여 시스템에 마운트하면 준비 끝

[root@gnode1 ~]# mkdir gluster_disk

[root@gnode1 ~]# cd gluster_disk/

[root@gnode1 ~]# dd if=/dev/zero of=./disk1.img bs=1M count=1024

[root@gnode1 ~]# losetup -f /root/gluster_disk/disk1.img

[root@gnode1 ~]# losetup

NAME       SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE

...

/dev/loop2         0      0         0  0 /root/gluster_disk/disk1.img 

[root@gnode1 ~]# mkfs.xfs /dev/loop2

[root@gnode1 ~]# mkdir /mnt/gnode_disk1

[root@gnode1 ~]# mount /dev/loop2 /mnt/gnode_disk1/



준비2: Password-less 로그인 설정

Geo-replication 이 가능하게 하기 위해서는, 2개의 서버에서 각각 상대방 서버로 패스워드 없이 root 계정으로 접속할 수 있도록 ssh 키를 교환해 두어야 한다. 다음과 같이 진행하자.


* Password-less ssh 접속을 위한 키 교환. 상대방 서버에 ssh 접속하고 암호를 입력해 두면 다음에는 암호 없이 로그인 가능

[root@gnode1 ~]# ssh-keygen 

[root@gnode1 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.10.10.11

[root@gnode1 ~]# ssh gnode2



Gluster Volume 생성 및 기동

각각의 Gluster 서버에서 다음과 같이 Volume 을 생성하고 기동한다. 


* 주의: 마운트포인트 아래에 서브디렉토리를 생성하여 볼륨(여기서는 distvol)에 할당해야 함

[root@gnode1 ~]# mkdir -p /mnt/gnode_disk1/brick

[root@gnode1 ~]# gluster volume create distvol gnode1:/mnt/gnode_disk1/brick

[root@gnode1 ~]# gluster volume start distvol

[root@gnode1 ~]# gluster volume info

Volume Name: distvol

Type: Distribute

Volume ID: 9754b79a-d0e7-4823-9e85-38340d99e732

Status: Started

Snapshot Count: 0

Number of Bricks: 1

Transport-type: tcp

Bricks:

Brick1: gnode1:/mnt/gnode_disk1/brick

Options Reconfigured:

nfs.disable: on

performance.readdir-ahead: on

transport.address-family: inet


[root@gnode2 ~]# mkdir -p /mnt/gnode_disk1/brick

[root@gnode2 ~]# gluster volume create distvol gnode2:/mnt/gnode_disk1/brick

[root@gnode2 ~]# gluster volume start distvol

[root@gnode2 ~]# gluster volume info

Volume Name: distvol

Type: Distribute

Volume ID: e12db607-2e19-4a45-bc3b-eb6e922f59e5

Status: Started

Snapshot Count: 0

Number of Bricks: 1

Transport-type: tcp

Bricks:

Brick1: gnode2:/mnt/gnode_disk1/brick

Options Reconfigured:

nfs.disable: on

performance.readdir-ahead: on

transport.address-family: inet



Geo-replication 설정 및 테스트

* Geo-replication 설정은 Master(여기서는 gnode1) 에서만 수행

[root@gnode1 ~]# gluster system:: execute gsec_create

[root@gnode1 ~]# gluster volume geo-replication distvol gnode2::distvol create push-pem

[root@gnode1 ~]# gluster volume geo-replication distvol gnode2::distvol start

[root@gnode1 ~]# gluster volume geo-replication distvol gnode2::distvol status

MASTER NODE    MASTER VOL    MASTER BRICK              SLAVE USER    SLAVE              SLAVE NODE    STATUS    CRAWL STATUS       LAST_SYNCED                  

-----------------------------------------------------------------------------------------------------------------------------------------------------

gnode1         distvol       /mnt/gnode_disk1/brick    root          gnode2::distvol    gnode2        Active    Changelog Crawl    2016-10-10 01:10:04

[root@gnode1 ~]# gluster volume info distvol

Volume Name: distvol

Type: Distribute

Volume ID: 9754b79a-d0e7-4823-9e85-38340d99e732

Status: Started

Snapshot Count: 0

Number of Bricks: 1

Transport-type: tcp

Bricks:

Brick1: gnode1:/mnt/gnode_disk1/brick

Options Reconfigured:

changelog.changelog: on

geo-replication.ignore-pid-check: on

geo-replication.indexing: on

nfs.disable: on

performance.readdir-ahead: on

transport.address-family: inet



동기화 테스트

동기화 테스트를 수행하기 위해 별도의 Gluster Client 를 사용해야 하지만, Gluster 서버 자신을 클라이언트로 사용해도 된다. 다시 말해 Gluster 서버에서 자신의 Volume 을 리모트 마운트하는 것이다. 다음과 같이 진행해 보자.


* gnode1: Gluster Fuse Client 로 distvol 볼륨을 로컬의 /mnt/gnode1_distvol/ 디렉토리로 마운트하고 해당 디렉토리로 이동

[root@gnode1 ~]# mkdir /mnt/gnode1_distvol

[root@gnode1 ~]# mount.glusterfs gnode1:/distvol /mnt/gnode1_distvol/

[root@gnode1 ~]# cd /mnt/gnode1_distvol/


* gnode2: Gluster Fuse Client 로 distvol 볼륨을 로컬의 /mnt/gnode2_distvol/ 디렉토리로 마운트하고 해당 디렉토리로 이동

[root@gnode2 ~]# mkdir /mnt/gnode2_distvol

[root@gnode2 ~]# mount.glusterfs gnode1:/distvol /mnt/gnode2_distvol/

[root@gnode2 ~]# cd /mnt/gnode2_distvol/


* gnode1: 파일 생성, 수정 및 삭제

[root@gnode1 ~]# echo abcdef----xyzxyz----xxxx > test.txt

[root@gnode1 ~]# cp test.txt s

[root@gnode1 ~]# cat test.txt >> s

[root@gnode1 ~]# cp s x

[root@gnode1 ~]# cat s >> x

[root@gnode1 ~]# vi x

[root@gnode1 ~]# rm tttt

...

[root@gnode1 ~]# gluster volume geo-replication distvol gnode2::distvol status

MASTER NODE    MASTER VOL    MASTER BRICK              SLAVE USER    SLAVE              SLAVE NODE    STATUS    CRAWL STATUS       LAST_SYNCED                  

-----------------------------------------------------------------------------------------------------------------------------------------------------

gnode1         distvol       /mnt/gnode_disk1/brick    root          gnode2::distvol    gnode2        Active    Changelog Crawl    2016-10-10 01:15:22


* gnode2 디렉토리 내의 파일 변화 관찰, 확인

[root@gnode2 gnode2_distvol]# ls

s  test.txt  x

[root@gnode2 gnode2_distvol]# cat test.txt

abcdef----xyzxyz----xxxx

[root@gnode2 gnode2_distvol]#


몇 가지 팁들 ... port 변경, 작은 파일에 유리한 option 등

Gluster 의 geo-replication은 내부적으로 ssh를 통한 rsync 방식으로 동작한다. 일반적으로 ssh는 TCP 22 포트를 사용하는데, 임의의 포트번호로 설정하려면 다음과 같이 진행하면 된다(사실, "How to replicate to slave via non-standard ssh port..." 에 대한 답이다).


우선 양쪽 Gluster 서버에서 ssh port 를 변경한다


# vi /etc/ssh/sshd_config

...

Port 22222

...


# systemctl restart sshd

# netstat -anp | grep 22222

tcp   0   0   0.0.0.0:22222    0.0.0.0:*    LISTEN    8425/sshd

...


만약 netstat 결과에 해당 포트번호로 바인딩되어 있지 않고 ssh 접속이 안된다면 SELINUX가 ssh의 기본 Port인 22번으로 바인딩하게 고정해 둔 것일 게다. 그러면 다음과 같은 semanage 명령을 수행하고 조금 시간이 지난 뒤에 다시 접속해 본다.


# semanage port -a -t ssh_port_t -p tcp 22222


Linux 서버에서 파이어월(firewalld)이 사용되고 있다면 다음과 같이 해당 포트를 허용해 주는 것도 잊지 않도록 하자.


# firewall-cmd --permanent --zone=public --add-port=22222/tcp

# firewall-cmd --reload


성공적으로 ssh 포트 번호를 변경하였다면, 이번에는 Gluster 에서 새로운 포트로 Geo-replication 이 되도록 설정하면 된다.


[root@gnode1 ~]# gluster volume geo-replication distvol gnode2::distvol stop

[root@gnode1 ~]# gluster volume geo-replication distvol gnode2::distvol delete

[root@gnode1 ~]# gluster volume geo-replication distvol gnode2::distvol create ssh-port 22222 push-pem

[root@gnode1 ~]# gluster volume geo-replication distvol gnode2::distvol config ssh-port 22222

[root@gnode1 ~]# gluster volume geo-replication distvol gnode2::distvol start



Rsync는 Block 방식의 전송이기 때문에 파일의 크기가 큰 경우에 좀 더 유리하다. 반대로 말하면 크기가 작은(대략 수 KB~수백KB 이내) 파일의 경우 전송 지연이 발생할 수 있게 되는데, 이 때에는 다음과 같은 옵션을 적용하여 테스트해 보기를 권한다. Geo-replication 이 작동되는 도중에도 실행해 볼 수 있다.


[root@gnode1 ~] gluster volume geo-replication distvol gnode2::distvol config use-tarssh true


참고로, 간단히 테스트 해 보니 50KB 크기의 몇 백 개 파일 전송에 1분 26초 정도 걸리던 것이, 옵션 변경 후에 약 10초 가량 전송 시간이 줄어든 것을 볼 수 있었다.


원래의 기본 전송방식인 Rsync 방식으로 돌아가려면 다음과 같이 하면 된다.

[root@gnode1 ~] gluster volume geo-replication distvol gnode2::distvol config \!use-tarssh



Good Luck !


- Barracuda -


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

Barracuda

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


9월 6일자 티스토리 공지사항에 보면 아래의 내용이 올라와 있다. 밥벌이에 바쁘다 보니 뒤늦게 읽게 됐는데, 보다 보니 좀 심각한 내용이다. 티스토리의 서버가 이전함에 따라 IP가 바뀌게 되어 2차 도메인 사용자를 위한 배려(?)로 CNAME으로 host.tistory.io 만을 사용하고 도메인에 대한 A 레코드(티스토리의 IP주소로 등록한 부분)을 삭제 해 달라는 내용이다(10월6일 이후에 좀 더 상세하게 바뀐 내용으로 업데이트된 공지 참고).


그런데, DNS 표준 권고사항에서는 루트도메인(Root Domain=Naked Domain=Bare Domain=Zone Apex ... 다 같은 말이다. 즉 필자의 경우를 예로 들면, 보유 중인 2차 개인 도메인인 'bryan.wiki'가 바로 루트도메인)에 대한 CNAME 은 허용하지 않게 되어 있다(참고: RFC1034, Serverfault 포스팅).




다시 풀어서 예를 들어 보면, www.bryan.wiki 에 대해서는 CNAME 으로 host.tistory.io 를 등록할 수 있지만, bryan.wiki 에 대한 CNAME 으로 host.tistory.io 를 등록하지 못하게 되어 있다. 표준을 준수하는 메이저 DNS 제공 업체들(Ghandi, GoDaddy 등)은 이러한 등록을 시도하지 못하도록 오류를 뱉어내게 되어 있다. Amazon의 Route53(Ghandi 의 DNS 서비스사용) 의 관리 화면에서 @.bryan.wiki(또는 그냥 bryan.wiki) 에 대해 CNAME을 등록할 경우에 붉은 글씨의 오류 메시지를 볼 수 있다.


티스토리가 CNAME 만의 사용을 계속 강요한다면...?


만약 티스토리가 A 레코드를 삭제하고 bryan.wiki 에 대한 주소 등록을 CNAME 만으로 등록하도록 입장을 고수한다면, 다음의 우회적인 방법으로 구현은 가능하다. 필자가 사용중인 Amazon의 Route53과 S3 Redirection에 의한 방법을 예로 들어 보겠다.


우선 AWS 웹사이트에서 Route53 의 도메인 관리화면으로 들어간다. 일단, Create Record Set에서 다음과 같이 www.bryan.wiki 에 대한 CNAME은 host.tistory.io. 으로 등록은 가능하다. 이 부분은 표준에 어긋나지 않으므로...




다음으로, 티스토리가 요구하는 대로 현재 등록된 A 레코드를 삭제해 보자.


이제 "http://www.bryan.wiki 로는 블로그 접속이 되지만, http://bryan.wiki 로는 접속이 되지 않는다. 티스토리에서 원하는 대로 bryan.wiki 에 대한 CNAME 등록을 시도해 본다.


Create 를 눌러 보지만 등록 불가라고 붉은 글씨의 오류가 나며, 다음 단계로 진행되지 않는다. 빈칸 자리에 @ 를 넣으면 등록은 가능하지만 여전히 http://bryan.wiki 로 블로그 접속은 불가한 상태!



S3 의 Redirect request 를 써 보자


S3 는 Amazon의 스토리지 공간(Bucket)에 파일을 올려 두고 인터넷을 통해 다운로드 등의 서비스가 가능한 서비스로 알려져 있지만, 버킷 접속을 특정 주소로 전환해 주는 기능도 가지고 있다. 이 기능을 사용하면 되지만, 사용량에 따른 비용을 내야 하는 유료 서비스이다. 접속 형태와 지역에 따라 구분이 되기는 하지만 서울 Region 기준으로 1만 건당 0.37 센트(무려!!!)를 지불해야 한다.


AWS 메인 화면에서 S3를 클릭하여 해당 화면에 진입한다. Create Bucket을 선택해서 Bucket Name에 2차도메인을 입력하고 Create 클릭.

  

Bucket 접속에 대한 Redirection End Point를 설정해 보자. Bucket 왼쪽의 돋보기 아이콘이나 오른쪽 위의 Properties를 선택하고 Redirect all requests to .... 를 선택하고 Save.




이제 http://bryan.wiki 에 접속했을 때 리디렉션할 주소가 생성되었으니, Route53 에서 Create Record Set으로 루드도메인에 대한 A 레코드를 선택하고 Alias를 선택하면, 아래에 S3 endpoint가 목록에 나타난다. 이를 선택하고 Create!


여기까지 성공하였다면 다음 그림과 같은 등록 상태를 확인할 수 있을 것이다. SOA, NS, CNAME, A 타입의 레코드가 각각 하나씩이다. 이제 http://bryan.wiki 와 같은 2차 도메인으로 접속하면, CNAME으로 등록한 www 서브도메인(http://www.bryan.wiki)으로 리디렉션되고 티스토리 블로그로 접속할 수 있게 된다.



10월 6일 전후로 티스토리에서 해당 공지를 변경하여 발표했다. A 레코드에 의한 DNS 등록을 병행하기로 했다는 내용이다. 처음 발표된 9월 6일 이후로 수많은 2차도메인 사용자들에게 혼란을 초래해 왔고, 또 많은 이들의 지적과 불만에 찬 댓글에 대한 리액션인 듯 하다.


티스토리가 서버를 이전하는 일에는 인터넷 서비스회사의 특성상 그럴 수도 있다고 하지만 앞으로 IP 주소가 얼마나 자주 바뀔 예정이기에 표준 준수를 어겨가면서까지 A 레코드 방식을 버리라고 무리수를 두었는지는 도대체 지금도 알 길이 없다. 그냥 단순한 사용자 편의만을 위한 배려뿐이었을까?


- Barracuda -


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

Barracuda

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


1. 오픈스택이란?

Openstack은 소규모부터 중대규모를 아우르는 IAAS(Infra As A Service)를 가능하게 하는 오픈소스 클라우드 플랫폼 솔루션입니다. 2010년, Rackspace사의 클라우드 파일 플랫폼과 NASA의 Nebula를 기반으로 통합, 협력하는 방식으로 진행되기 시작한 이 프로젝트의 첫 코드명은 Austin(2010). 이후 Diablo(2011), Essex(2012), Havana(2013), Icehouse, Juno(2014), Kilo(2015), Liverty, Mitaka(2016) 를 거져 2016년 10월에 Newton 버전이 출시될 예정으로 개발이 진행되고 있지요.


Python 언어를 기반으로 만들어진 이 클라우드 플랫폼은 Apache License 2.0 이 적용되어 있기 때문에 상용/비상용에 관계 없이 누구나 자유로이 채택하여 변경, 사용할 수 있을 뿐 아니라, 완성도 면에서 충실하고 잘 관리된 프로젝트의 퀄리티로 인해 수많은 전문가와 기업들이 관심을 가지고 개발에 참여하거나 자사의 제품에서 오픈스택을 지원하기 위해 연구개발을 진행하고 있는 Hot 한 기술 아이템이자 클라우드 컴퓨팅 플랫폼과 네트워킹 분야의 패러다임을 이끌고 있는 녀셕이기도 합니다.


원래는 Spec이 어느 정도 되는 서버 장비가 최소 2대 이상 있어야 설치, 운영을 해 볼 수 있지만, 가정 또는 소규모 연구실에서도 자체 Lab Test 정도는 해 볼 수 있도록 Triple-O(Openstack On Openstack) 구성에 대한 문서를 오픈스택 진영에서 직접 소개하고 있읍니다.

참고: ☞ https://wiki.openstack.org/wiki/TripleO).


2. 들어가며

본 포스팅에서는 필자의 맥북(MacBook Pro Sierra, Quadcore 16GB, SSD 256GB) 상에서 (1) VirtualBox 로 가상머신(CentOS 7.2)을 만들고, (2) Packstack을 이용해서 각각의 가상머신을 Openstack용 controller, Network, Compute 노드로 설치, 설정하여, 만들어진 오픈스택 클라우드 환경에서 (3) 실제로 VM을 생성하고 네트워크로 접속해보고, (4) LBaaS 서비스를 설치한 뒤 LB와 2개의 VM을 이용한 로드밸런싱 환경을 간단히 테스트해 보는 과정 전체를 기술해 둡니다.


중요한 점은, 본인이 포스팅하는 대다수의 블로깅에서 일관되게 지키려고 하는 원칙 중의 하나이긴 하지만, 과정 하나 하나를 직접 내 손으로 수행해 본 결과를 그대로 표현하고 기술함으로써 설치나 설정 과정에서 필요한 원리와 방식에 대해 더 구체적으로 들여다볼 수 있는 기회를 보는 이에게 제공해 주고자 한다는 점입니다. 내가 알지 못하고 무언가를 표현하는 것은, 보는 이에게 도움도 되지 않을 뿐 아니라 시간과 지면 낭비라고 생각하며, 플라스크를 만든 아르민 로나허가 말했듯이, "추상적인 내용보다 구체적인 경험을 나누는 것" 이 더 효과적인 소통과 전달의 수단이 될 수 있기 때문이지요.




본 포스팅의 작업과정에 가장 핵심적인 내용들이 위의 그림에 요약되어 표현되어 있으니, 잘 이해되도록 그림을 자세히 들여다 보기를 권하며, 아래 내용부터는 간략히, 다소 간결한 표현으로 써내려 갑니다.



3. 버추얼박스 가상머신 준비



우선 가상머신에 설치할 OS인 CentOS 7.2 를 다운로드(일반적으로 Ubuntu일 경우 Devstack 을 통해서, CentOS 나 Redhat일 경우 Packstack 을 통해서 Openstack 을 설치하게 됨).


  • VirtualBox 에서 VM 생성


위 그림에 해당하는 4개의 VM을 생성한다. Control 노드는 메모리를 4GB~8GB 정도 주는 것이 좋으며, Admin은 버추얼박스에서 GUI(웹브라우저 사용)이 가능하도록 데스크톱 형태로 설치해야 한다.


  • VM 네트워크 카드(NIC) 설정

그림을 잘 들여다 보면 기본적인 설정사항이 상세히 표시되어 있는데, 본 포스팅에서는 VirtualBox 에서 3가지의 네트워크(각각 외부, 데이터, 관리용)를 준비해야 한다. 이 중에서 외부(External) 네트워크는 인터넷 접속용, 데이터는 VM에 할당되는 서비스 네트워크, 관리용은 Openstack이 VM을 관리하기 위한 용도.

데이터 및 관리용 네트워크는 VirtualBox 의 호스트전용 어댑터 방식(NAT방식으로 해도 작동은 하지만, Virtualbox 내부에서만 동작하는 네트워크이므로 이 정도로 충분함)으로 생성하고, 외부용 네트워크는 맥북 호스트에서 VM에 접속도 가능하고, VM에서 외부 인터넷으로도 접속이 가능하도록 브리지모드로 생성.

호스트전용 어댑더 방식을 위해 아래 그림의 과정과 같이 vboxnet0 네트워크를 우선 생성한다(VirtualBox 환경설정 > 툴바의 네트워크 선택 > 호스트전용 네트워크 선택 > 우측 + 아이콘 클릭).


IPv4 주소는 변경할 필요 없이 그대로 둔다.


DHCP 서버를 눌러서 내용 확인 후 변경 없이 그대로 확인


마우스 커서를 들이대면 설정된 네트워크 정보가 보인다. 그냥 확인.


VM에서 사용할 호스트전용 네트워크가 준비되었으므로 다음 섹션의 VM별 네트워크 구성표에 따라 각각 어댑터를 할당하고 맨 위의 구성도를 보면서 스텍을 적당히 조절하며 4개의 VM을 생성한다. 앞의 3개의 노드들은 가급적 GUI가 없는 서버모드로 OS를 설치해야 하며, 마지막의 Admin VM은 웹브라우저를 통해 Controller VM의 Horizon 웹포탈에 접속해야 하므로 GUI 가 가능하도록 생성하되, 네트워크 정보는 Controller 와 같은 구성으로 하면 운영, 관리에 무리가 없을 것이다.


  • 각 VM 네트워크 카드(NIC) 구성

VM구분

(노드)

 NIC1

 NIC2

 NIC3

방식

(vbox)

장치명

용도

방식

(vbox)

장치명

용도

방식

(vbox)

장치명

용도

 Controller

bridge

enp0s9

API

(외부)

vboxnet0

eth0

control

(관리용)

 

 

 

 Network

bridge

enps09

API

(외부)

vboxnet0

eth0

control

(관리용)

vboxnet0

eth1

tenant

(데이터)

 Compute

 

 

 

vboxnet0

eth0

control

(관리용)

vboxnet0

eth1

tenant

(데이터)


   * 용도별 network segment(IP 대역)

     - API(외부용): 192.168.0.6~ ... Vbox VM외부, 즉 Virtualbox(즉 호스트의 하이퍼바이저 레벨) 에서는 bridge 장치로 인식되며, VM 내부에서는 ifcfg-enp0s9 장치로 관리됨(물리장치명 enp0s9 는 맥북의 경우이며, 일반 조립PC의 RTL 계열 등의 네트워크 카드는 단순히 eth0 와 같은 명칭이 될 수도 있다)

     - Control(관리용): 10.10.10.6~ ... Vbox VM 내부에서는 ifcfg-eth0 로 관리됨

     - Tenant(데이터): 10.10.20.6~ ... Vbox VM 내부에서는 ifcfg-eth1 으로 관리됨

   * 모든 Adapter 는 Virtualbox 에서 생성시 '무작위모드 허용' 으로 설정

   * vboxnet0 방식의 NIC은 모두 '반가상네트워크'로 설정


글 처음에 보이는 시스템 구성도와 아래의 네트워크 구성도는 Openstack 설치, 설정과정의 핵심 구조와 원리를 담은 설계도에 해당하므로 충분히 들여다 보고 잘 이해해 두도록 한다 그래야만 차후에 본인 스스로 실무적 요구사항을 담은 구성도를 직접 그려낼 수 있게 될 것이다.




4. 오픈스택 노드 들의 네트워크 개별 설정 - (1)


이제 위의 구성도와 표에 맞게 각 노드의 네트워크를 세부적으로 설정해 보자


  • 인터넷에서 패키지를 다운로드할 수 있도록 DNS 접속 주소를 설정

# vi /etc/resolv.conf # --> 자신의 인터넷 환경에 맞게 DNS 등록. 잘 모르면 8.8.8.8 하나만.

nameserver 168.126.63.1

nameserver 168.126.63.2


  • Controller 노드

  호스트명을 다음과 같이 변경하고 로그아웃 후, 다시 로그인


# hostnamectl set-hostname openstack-ctrl


  API용 외부 네트워크(enp0s9)

[root@openstack-ctrl ~]# vi /etc/sysconfig/network-scripts/ifcfg-enp0s9

DEVICE=enp0s9

TYPE=Ethernet

ONBOOT=yes

NM_CONTROLLED=no

IPADDR=192.168.0.6

NETMASK=255.255.255.0

[root@openstack-ctrl ~]# vi /etc/sysconfig/network-scripts/route-enp0s9

default via 192.168.0.1 dev enp0s9

 

  관리용 내부 네트워크(eth0)

[root@openstack-ctrl ~]# vi /etc/sysconfig/network-scripts/ifcfg-eth0

DEVICE=eth0

TYPE=Ethernet

ONBOOT=yes

NM_CONTROLLED=no

IPADDR=10.10.10.6

NETMASK=255.255.255.0


[root@openstack-ctrl ~]# systemctl restart network

[root@openstack-ctrl ~]# yum install -y net-tools


  Controller 노드는 Openstack 인프라 내에서 시간동기화(ntpd) 서버 기능을 해야 하므로 다음과 같이 ntpd 서버로 설정한다


[root@openstack-ctrl ~]# yum clean all; yum install -y ntp

[root@openstack-ctrl ~]# vi /etc/ntp.conf

restrict 10.10.10.0 mask 255.255.255.0 nomodify notrap

server kr.pool.ntp.org

server time.bora.net

server time.kornet.net

[root@openstack-ctrl ~]# systemctl enable ntpd

[root@openstack-ctrl ~]# systemctl start ntpd

[root@openstack-ctrl ~]# ntpq -pn


  • Network 노드

  호스트명을 다음과 같이 변경하고 로그아웃 후, 다시 로그인


# hostnamectl set-hostname openstack-net


  API용 외부 네트워크(enp0s9)

[root@openstack-net ~]# vi /etc/sysconfig/network-scripts/ifcfg-enp0s9

DEVICE=enp0s9

TYPE=Ethernet

ONBOOT=yes

NM_CONTROLLED=no

IPADDR=192.168.0.7

NETMASK=255.255.255.0

[root@openstack-net ~]# vi /etc/sysconfig/network-scripts/route-enp0s9

default via 192.168.0.1 dev enp0s9

 

  관리용 내부 네트워크(eth0)

[root@openstack-net ~]# vi /etc/sysconfig/network-scripts/ifcfg-eth0

DEVICE=eth0

TYPE=Ethernet

ONBOOT=yes

NM_CONTROLLED=no

IPADDR=10.10.10.7

NETMASK=255.255.255.0


  데이터용 내부 네트워크(eth1) 

[root@openstack-net ~]# vi /etc/sysconfig/network-scripts/ifcfg-eth1

DEVICE=eth1

TYPE=Ethernet

ONBOOT=yes

NM_CONTROLLED=no

IPADDR=10.10.20.7

NETMASK=255.255.255.0


[root@openstack-net ~]# systemctl restart network

[root@openstack-net ~]# yum install -y net-tools


  Controller 노드에 맞추어 시간동기화(ntp) 기능을 설정


[root@openstack-net ~]# yum clean all; yum install -y ntp

[root@openstack-net ~]# vi /etc/ntp.conf

server  10.10.10.6

[root@openstack-net ~]# systemctl enable ntpd

[root@openstack-net ~]# systemctl start ntpd

[root@openstack-net ~]# ntpq -pn

     remote           refid      st t when poll reach   delay   offset  jitter

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

 10.10.10.6      LOCAL(0)         6 u  92m   64    0   60.402   37.293   0.000


5. 오픈스택 설치를 위한 각 노드 계정 및 네트워크 설정 - (2)


  • Controller, Network, Compute 노드에서 각각 오픈스택 설치/운영을 위한 stack 이라는 계정을 생성하고 권한을 부여
# useradd stack
# passwd stack  # --> specify a password
# echo "stack ALL=(root) NOPASSWD:ALL" | sudo tee -a /etc/sudoers.d/stack
# sudo chmod 0440 /etc/sudoers.d/stack

  • Compute 노드는 외부 네트워크에 직접 연결할 수 있는 NIC이 없음(위의 표: VM NIC 구성표 참조)


시스템 구성도와는 다르게 Compute 노드에 Network 노드처럼 NIC을 하나 더 추가해 주면 되지만, 여기서는 원칙대로 진행한다. 하지만 이렇게 되면 Compute 노드에서 새로운 패키지를 설치할 수 없게 되므로, Network 노드(를 게이트웨이로 해서)를 통해 외부 접속이 가능하도록 해 보자. 우선, Network 노드에서 다음을 수행한다.


# yum install -y iptables-services

# systemctl start iptables

# systemctl enable iptables

# iptables -F

# iptables -t nat -A POSTROUTING -o enp0s9 -j MASQUERADE

# iptables -A FORWARD -i eth0 -j ACCEPT

# service iptables save

# vi /etc/sysctl.conf

net.ipv4.ip_forward=1

# sysctl -p

   ※ [주의1] 붉은 글씨로 표기된 enp0s9 장치명은 Openstack 설치가 완료된 이후에는 br-ex 로 바꿔서 사용되어야 한다. 아래에서 다시 설명함

  • Compute 노드(openstack-cmp1, openstack-cmp2, ...)

  호스트명을 다음과 같이 변경하고 로그아웃 후, 다시 로그인


# hostnamectl set-hostname openstack-cmp1



  데이터용 내부 네트워크(eth1) 

[root@openstack-cmp1 ~]# vi /etc/sysconfig/network-scripts/ifcfg-eth1

DEVICE=eth1

TYPE=Ethernet

ONBOOT=yes

NM_CONTROLLED=no

IPADDR=10.10.20.8

NETMASK=255.255.255.0


     다음으로 Compute 노드에서 다음과 같이 네트워크와 게이트웨이를 설정한다.

# vi /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0

TYPE=Ethernet

ONBOOT=yes

NM_CONTROLLED=no

IPADDR=10.10.10.8

NETMASK=255.255.255.0

# vi /etc/sysconfig/network-scripts/route-eth0

default via 10.10.10.7 dev eth0

# systemctl restart network
# yum install -y net-tools

# ping 8.8.8.8

PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.

64 bytes from 8.8.8.8: icmp_seq=1 ttl=53 time=45.7 ms

64 bytes from 8.8.8.8: icmp_seq=2 ttl=53 time=39.8 ms ...


  Controller 노드에 맞추어 시간동기화(ntp) 기능을 설정한다


[root@openstack-cmp1 ~]# yum clean all; yum install -y ntp

[root@openstack-cmp1 ~]# vi /etc/ntp.conf

server  10.10.10.6

[root@openstack-cmp1 ~]# systemctl enable ntpd

[root@openstack-cmp1 ~]# systemctl start ntpd

[root@openstack-cmp1 ~]# ntpq -pn

     remote           refid      st t when poll reach   delay   offset  jitter

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

 10.10.10.6      LOCAL(0)         6 u  92m   64    0   60.402   37.293   0.000


6. 오픈스택 설치를 위한 각 노드들의 패키지 설정


  • 각 노드의 리포지토리를 변경하고 필수 패키지를 설치


이제 모든 3개 노드(Controller, Network, Compute)에서 BaseRepo 를 다음과 같이 변경하고, 필수 유틸리티와 종속성 패키지를 순서대로 빠짐 없이 설치한다(Openstack 버전에 따라 다소 차이가 있을 수 있으며, 아래는 2016.9월 시점 Mitaka v5 기준). 참고로 활발히 개발이 진행되고 있는 Openstack 의 버전에 따라, 본 포스팅의 경우처럼 다수의 의존성 패키지를 따로 설치하지 않고 yum update 를 통해 기본적인 패키지 업데이트를 진행한 다음, 최소한의 의존성 패키지를 따로 설치하는 경우도 생각해 볼 수 있다.


# vi /etc/yum.repos.d/CentOS-Base.repo

#mirrorlist=...모든 섹션에서 mirrorlist 줄을 comment

#baseurl=...모든 섹션에서 baseurl 줄을 comment, 그 줄 아래에 다음 줄을 각각 추가

baseurl=http://ftp.daumkakao.com/centos/$releasever/os/$basearch/

# yum install -y net-tools

# yum install -y wget


# --> 종속성 패키지 설치

# wget http://mirror.centos.org/centos/7/extras/x86_64/Packages/python-markdown-2.4.1-1.el7.centos.noarch.rpm

# wget http://mirror.centos.org/centos/7/extras/x86_64/Packages/python-cheetah-2.4.4-5.el7.centos.x86_64.rpm

# wget http://mirror.centos.org/centos/7/extras/x86_64/Packages/python-werkzeug-0.9.1-2.el7.noarch.rpm

# wget http://mirror.centos.org/centos/7/extras/x86_64/Packages/python-itsdangerous-0.23-2.el7.noarch.rpm

# wget http://mirror.centos.org/centos/7/extras/x86_64/Packages/python-flask-0.10.1-4.el7.noarch.rpm

# wget wget ftp://mirror.switch.ch/pool/4/mirror/centos/7.2.1511/os/x86_64/Packages/python-jinja2-2.7.2-2.el7.noarch.rpm

# wget ftp://mirror.switch.ch/pool/4/mirror/centos/7.2.1511/cloud/x86_64/openstack-liberty/common/python-babel-1.3-6.el7.noarch.rpm

# wget ftp://mirror.switch.ch/pool/4/mirror/centos/7.2.1511/os/x86_64/Packages/python-markupsafe-0.11-10.el7.x86_64.rpm

# sudo yum install -y python-pygments

# sudo rpm -ivh python-markdown-2.4.1-1.el7.centos.noarch.rpm

# sudo rpm -ivh python-cheetah-2.4.4-5.el7.centos.x86_64.rpm

# sudo rpm -ivh python-werkzeug-0.9.1-2.el7.noarch.rpm

# sudo rpm -ivh python-itsdangerous-0.23-2.el7.noarch.rpm

# sudo yum install -y  pytz

# sudo rpm -ivh python-babel-1.3-6.el7.noarch.rpm

# sudo rpm -ivh python-markupsafe-0.11-10.el7.x86_64.rpm

# sudo rpm -ivh python-jinja2-2.7.2-2.el7.noarch.rpm

# sudo rpm -ivh python-flask-0.10.1-4.el7.noarch.rpm


  • 각 노드의 /etc/hosts 파일에 다음의 3개 라인을 추가

10.10.10.6 openstack-ctrl

10.10.10.7 openstack-net

10.10.10.8 openstack-cmp1


  • 마찬가지로 각 3개 노드에서 보안 등 각종 관리패키지를 오픈스택 설치가 가능해 지도록 구성

# systemctl disable firewalld

# systemctl stop firewalld

# systemctl disable NetworkManager

# systemctl stop NetworkManager

# yum remove -y NetworkManager

# systemctl enable network

# systemctl start network


# vi /etc/selinux/config #--> liberty 부터는 불필요 해졌으므로 생략

...

SELINUX=permissive

...



7. Packstack 으로 Openstack 자동 설치하기

Packstack은 Controller 노드에서 answer 파일을 읽어 들여서 자신을 포함하여 하위 노드(Network 노드, Compute 노드들)에서 각종 소프트웨어들을 설치/설정하는 스크립트를 단계적으로 실행해 주는 역할을 한다(Redhat 제공 RDO 프로젝트 참고: www.rdoproject.org). 이를 위해 각 하위 노드에 암호 없이 접속이 가능해야 한다. 


  • Controller 노드에서 각 노드로 Passwordless ssh 설정

[root@openstack-ctrl ~]# ssh-keygen 

[root@openstack-ctrl ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.10.10.7

[root@openstack-ctrl ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@10.10.10.8


#--> 암호 없이 정상적으로 접속되는지 확인

[root@openstack-ctrl ~]# ssh openstack-net

[root@openstack-ctrl ~]# ssh openstack-cmp1


  • Packstack repo 설정 및 설치 시작

[root@openstack-ctrl ~]# yum install -y https://www.rdoproject.org/repos/rdo-release.rpm

[root@openstack-ctrl ~]# yum install -y openstack-packstack

[root@openstack-ctrl ~]# su - stack

[root@openstack-ctrl ~]# sudo packstack --gen-answer-file=answer.txt

[root@openstack-ctrl ~]# sudo vi answer.txt

=== OVS(Openvswitch) 기반 샘플 answer 파일 다운로드 ===

=== answer.txt 로 복사후 편집, 사용 ===


[root@openstack-ctrl ~]# sudo packstack --answer-file=answer.txt

Welcome to the Packstack setup utility



The installation log file is available at: /var/tmp/packstack/20160828-041734-_nTMcZ/openstack-setup.log



Installing:

Clean Up                                             [ DONE ]

Discovering ip protocol version                      [ DONE ]

Setting up ssh keys                                  [ DONE ]

Preparing servers                                    [ DONE ]

Pre installing Puppet and discovering hosts' details [ DONE ]

Adding pre install manifest entries                  [ DONE ]

Installing time synchronization via NTP              [ DONE ]

Setting up CACERT                                    [ DONE ]

Adding AMQP manifest entries                         [ DONE ]

Adding MariaDB manifest entries                      [ DONE ]

Adding Apache manifest entries                       [ DONE ]

Fixing Keystone LDAP config parameters to be undef if empty[ DONE ]

Adding Keystone manifest entries                     [ DONE ]

...

Adding Nagios server manifest entries                [ DONE ]

Adding Nagios host manifest entries                  [ DONE ]

Copying Puppet modules and manifests                 [ DONE ]

Applying 10.10.10.8_prescript.pp

Applying 10.10.10.7_prescript.pp

Applying 10.10.10.6_prescript.pp

10.10.10.8_prescript.pp:                             [ DONE ]      

10.10.10.6_prescript.pp:                             [ DONE ]      

10.10.10.7_prescript.pp:                             [ DONE ]      

Applying 10.10.10.8_chrony.pp

Applying 10.10.10.7_chrony.pp

Applying 10.10.10.6_chrony.pp

10.10.10.8_chrony.pp:                                [ DONE ]   

10.10.10.6_chrony.pp:                                [ DONE ]   

10.10.10.7_chrony.pp:                                [ DONE ]   

Applying 10.10.10.6_amqp.pp

Applying 10.10.10.6_mariadb.pp

10.10.10.6_amqp.pp:                                  [ DONE ]    

10.10.10.6_mariadb.pp:                               [ DONE ]    

Applying 10.10.10.6_apache.pp

10.10.10.6_apache.pp:                                [ DONE ]   

Applying 10.10.10.6_keystone.pp

Applying 10.10.10.6_glance.pp

Applying 10.10.10.6_cinder.pp

10.10.10.6_keystone.pp:                              [ DONE ]     

10.10.10.6_glance.pp:                                [ DONE ]     

10.10.10.6_cinder.pp:                                [ DONE ]     

Applying 10.10.10.6_api_nova.pp

10.10.10.6_api_nova.pp:                              [ DONE ]     

Applying 10.10.10.6_nova.pp

Applying 10.10.10.8_nova.pp

10.10.10.6_nova.pp:                                  [ DONE ] 

10.10.10.8_nova.pp:                                  [ DONE ] 

Applying 10.10.10.8_neutron.pp

Applying 10.10.10.7_neutron.pp

Applying 10.10.10.6_neutron.pp

10.10.10.8_neutron.pp:                               [ DONE ]    

10.10.10.6_neutron.pp:                               [ DONE ]    

10.10.10.7_neutron.pp:                               [ DONE ]    

Applying 10.10.10.6_osclient.pp

Applying 10.10.10.6_horizon.pp

10.10.10.6_osclient.pp:                              [ DONE ]     

10.10.10.6_horizon.pp:                               [ DONE ]     

Applying 10.10.10.6_ring_swift.pp

10.10.10.6_ring_swift.pp:                            [ DONE ]       

Applying 10.10.10.6_swift.pp

10.10.10.6_swift.pp:                                 [ DONE ]  

Applying 10.10.10.6_heat.pp

10.10.10.6_heat.pp:                                  [ DONE ] 

Applying 10.10.10.6_provision.pp

Applying 10.10.10.6_provision_glance

10.10.10.6_provision.pp:                             [ DONE ]          

10.10.10.6_provision_glance:                         [ DONE ]          

Applying 10.10.10.7_provision_bridge.pp

10.10.10.7_provision_bridge.pp:                      [ DONE ]             

Applying 10.10.10.6_gnocchi.pp

10.10.10.6_gnocchi.pp:                               [ DONE ]    

Applying 10.10.10.6_mongodb.pp

Applying 10.10.10.6_redis.pp

10.10.10.6_mongodb.pp:                               [ DONE ]    

10.10.10.6_redis.pp:                                 [ DONE ]    

Applying 10.10.10.6_ceilometer.pp

10.10.10.6_ceilometer.pp:                            [ DONE ]       

Applying 10.10.10.6_aodh.pp

10.10.10.6_aodh.pp:                                  [ DONE ] 

Applying 10.10.10.6_nagios.pp

Applying 10.10.10.8_nagios_nrpe.pp

Applying 10.10.10.7_nagios_nrpe.pp

Applying 10.10.10.6_nagios_nrpe.pp

10.10.10.8_nagios_nrpe.pp:                           [ DONE ]        

10.10.10.7_nagios_nrpe.pp:                           [ DONE ]        

10.10.10.6_nagios.pp:                                [ DONE ]        

10.10.10.6_nagios_nrpe.pp:                           [ DONE ]        

Applying Puppet manifests                            [ DONE ]

Finalizing                                           [ DONE ]


 **** Installation completed successfully ******


Additional information:

 * Time synchronization installation was skipped. Please note that unsynchronized time on server instances might be problem for some OpenStack components.

 * File /root/keystonerc_admin has been created on OpenStack client host 10.10.10.6. To use the command line tools you need to source the file.

 * To access the OpenStack Dashboard browse to http://10.10.10.6/dashboard .

Please, find your login credentials stored in the keystonerc_admin in your home directory.

 * The installation log file is available at: /var/tmp/packstack/20160906-010456-B6ViYg/openstack-setup.log

 * The generated manifests are available at: /var/tmp/packstack/20160906-010456-B6ViYg/manifests



  • 유의해야 할 사항

본 글에서 제공되는 answer.txt 샘플파일은 2016.9월 시점 Mitaka 기준의 것으로, LBaas 설치 옵션(CONFIG_LBAAS_INSTALL)을 켜게 되면 설치 단계에서 "neutron server 실행 오류가 유발되고 있으므로 초기 설치시에는 일단 제외하고 LBaas는 글 후반에 별도로 다룸


Neutron L2 Agent와 Neutron ML2 Mechanism driver는 openvswitch만을 적용하였다.  메커니즘 드라이버는 필요에 따라 linuxbridge, arista, cisco nexus 등을 여건에 맞게 콤마로 구분하여 추가하면 됨


간혹 설치 중간 중간에 노드의 상태가 불안정하여 오류가 발생하거나 더 이상 진행되지 않는 등의 경우가 생기기도 하는데, 강제 종료 후 다시 시도해 보거나, 전체 노드를 리부트 후 재시도 또는 최악의 경우 VM을 다시 만들어 초기 상태에서 재시도를 해야 하는 경우가 있을 수도 있음


잦은 설치 오류로 인하여 /var/log 디렉토리의 서비스별 로그 디렉토리에 log 파일이 넘쳐서 Vbox VM의 디스크가 Full 될 수도 있음에 유의



8. Network 노드 - Neutron 네트워크(OVS bridge) 인터페이스 설정


이제 설치과정의 막바지. Network 노드의 브리지 인터페이스를 다음과 같이 설정해 주어야 한다 


  • Network 노드 브리지 인터페이스 설정

[root@openstack-net ~]# ifconfig

br-ex: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500

        inet6 fe80::b4c4:87ff:fe82:a949  prefixlen 64  scopeid 0x20<link>

        ether b6:c4:87:82:a9:49  txqueuelen 0  (Ethernet)

        RX packets 0  bytes 0 (0.0 B)

        RX errors 0  dropped 13  overruns 0  frame 0

        TX packets 8  bytes 648 (648.0 B)

        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0


enp0s9: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500

        inet 192.168.0.7  netmask 255.255.255.0  broadcast 192.168.0.255

        inet6 fe80::a00:27ff:fe2f:5037  prefixlen 64  scopeid 0x20<link>

        ether 08:00:27:2f:50:37  txqueuelen 1000  (Ethernet)

        RX packets 27435  bytes 37349433 (35.6 MiB)

        RX errors 0  dropped 0  overruns 0  frame 0

        TX packets 8894  bytes 643839 (628.7 KiB)

        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0


eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500

        inet 10.10.10.7  netmask 255.255.255.0  broadcast 10.10.10.255

         ...

        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0


eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500

        inet 10.10.20.7  netmask 255.255.255.0  broadcast 10.10.20.255

         ...

        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ifconfig 로 네트워크 구성을 살펴 보면 br-ex(외부 연결 브리지)에는 IP 주소가 할당되어 있지 않고, enp0s9 에 외부 연결용 IP(192.168.0.7)이 할당되어 있는 것을 볼 수 있다. 


이제 enp0s9 에 할당된 외부 네트워크 정보를 br-ex(외부용 브리지 인터페이스) 로 옮기고, ifcfg-enp0s9를 br-ex와 연결하는 작업을 해야 한다. 다음의 과정을 따라가 보자.


[root@openstack-net ~]# cd /etc/sysconfig/network-scripts/

[root@openstack-net ~]# cp ifcfg-enp0s9 ifcfg-br-ex

[root@openstack-net ~]# vi ifcfg-br-ex

DEVICE=br-ex

DEVICETYPE=ovs

TYPE=OVSBridge

BOOTPROTO=static

IPADDR=192.168.0.7

NETMASK=255.255.255.0

BROADCAST=192.168.0.255

GATEWAY=192.168.0.1

DEFROUTE=yes

ONBOOT=yes

NM_CONTROLLED=no

IPV4_FAILURE_FATAL=yes

IPV6INIT=no


[root@openstack-net ~]# vi ifcfg-enp0s9

DEVICE=enp0s9

DEVICETYPE=ovs

TYPE=OVSPort

OVS_BRIDGE=br-ex

ONBOOT=yes

NM_CONTROLLED=no

IPV6INIT=no



[root@openstack-net ~]# systemctl restart network

[root@openstack-net ~]# ifconfig

br-ex: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500

        inet 192.168.0.7  netmask 255.255.255.0  broadcast 192.168.0.255

        inet6 fe80::b49c:2aff:fe19:7e4c  prefixlen 64  scopeid 0x20<link>

        ether 08:00:27:2f:50:37  txqueuelen 0  (Ethernet)

        RX packets 31966  bytes 5704088 (5.4 MiB)

        RX errors 0  dropped 1  overruns 0  frame 0

        TX packets 7268  bytes 1153521 (1.1 MiB)

        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0



enp0s9: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500

        inet6 fe80::a00:27ff:fe2f:5037  prefixlen 64  scopeid 0x20<link>

        ether 08:00:27:2f:50:37  txqueuelen 1000  (Ethernet)

        RX packets 1191146  bytes 714542018 (681.4 MiB)

        RX errors 0  dropped 0  overruns 0  frame 0

        TX packets 17821  bytes 2222494 (2.1 MiB)

        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

네트워크 재시작 후 네트워크 구성을 ifconfig 로 확인해 보면, br-ex에 외부 연결용 네트워크 정보가 할당되어 있고, enp0s9의 ethernet(MAC) 주소가 br-ex 의 MAC 주소와 동일한 값을 가진 것을 확인할 수 있다.


9. Compute 노드 - 네트워크 게이트웨이 설정


  • Compute 노드에서 외부로 접속할 필요가 있을 경우에만 해당


앞서 설명했듯이 Compute 에는 외부로 직접 연결 가능한 네트워크가 없으므로, Network 노드를 통해서만 외부 접속이 가능하다.  위의 [주의1] 에서 설명한 부분의 파란 글씨에 해당하는 설정을 Network 노드에서 수행(위에서 설명한 대로 enp0s9는 br-ex 로 변경)한 다음 다음과 같이 설정해 두면 된다.


[root@openstack-cmp1 ~]# cd /etc/sysconfig/network-scripts/

[root@openstack-cmp1 ~]# vi ifcfg-eth0

DEVICE=eth0

TYPE=Ethernet

ONBOOT=yes

NM_CONTROLLED=no

IPADDR=10.10.10.8

NETMASK=255.255.255.0


[root@openstack-cmp1 ~]# vi route-eth0

default via 10.10.10.7 dev eth0


[root@openstack-cmp1 ~]# systemctl restart network



Controller 노드의 환경변수 설정 스크립트 복사


  • Network 노드, Compute 노드에서 CLI 명령 실행이 필요할 경우에 대비하여 스크립트 복사


Controller 노드의 keystonerc 관련 파일들을 다음과 같이 Network 노드, Compute 노드에 복사해 둔다


[root@openstack-ctrl ~]# scp keystonerc_* root@openstack-net:~

[root@openstack-ctrl ~]# scp keystonerc_* root@openstack-cmp1:~



10. Horizon 웹을 통해 Cloud 플랫폼 사용 준비 과정


  • Horizon 웹 UI 사용 준비 - 클라우드 네트워크 설정


시스템 구성도의 Admin VM에 해당하는 VM에서, firefox 웹브라우저를 통해 http://10.10.10.6으로 접속, admin 계정과 answer.txt 에서 지정한 암호를 통해 로그인


tenant 서비스를 위해서 demo 프로젝트를 사용한다고 가정했을 때,  해당 프로젝트에서 사용할 네트워크를 생성, 설정하는 과정을 다음과 같이 진행한다.



사용하지 않는 기본 Router 삭제(Admin->System->Router->demo 프로젝트-public라우터), Public 네트워크 정보(Admin->System->Network->admin 프로젝트-public subnet)을 삭제 



networks 패널에서, '+Create Network' 을 선택하고 admin 프로젝트에 대해 public 네트워크를 새로이 생성(본 케이스에서는 vxlan 방식, shared - External network).



생성된 public 네트워크 항목을 선택하여, 하단의 '+Create Subnet' 을 클릭,  실제로 사용할 외부 네트워크 정보(CIDR, Gateway, DNS 등)을 Subnet, Subnet Detail 항목 선택을 통해 각각 등록


admin 계정을 로그아웃하고, tenant 서비스를 설정/사용하기 위한 demo 계정으로 로그인

앞에서 admin 을 통해 생성, 설정한 네트워크를 사용하기 위해, 왼쪽 메뉴에서 Project->Network->Router 패널을 선택하고 '+Create Router' 을 클릭하려 demo 프로젝트에서 사용할 라우터를 생성. External Network 항목에 대해, 앞서 생성했던 public 네트워크를 선택



'+Create Gateway' 클릭을 통해 외부 네트워크와의 연결을 위한 게이트웨이를 설정




'+Add Interface' 클릭을 통해, 앞서 생성한 사설 네트워크와의 연결을 위한 인터페이스를 추가


좌측 메뉴의 Network 패널에서 Network Topology 를 선택하여 현재 구성된 네트워크의 구조를 확인한다. 여기까지 성공적으로 진행하였다면 클라우드 환경을 본격적으로 사용하기 위한 전체 설정 과정은 완료한 것이며, 다음의 필수 준비과정 몇 가지를 거치면 모든 준비가 끝나고 VM을 생성, 관리할 수 있게 됨.


11. VM 인스턴스 생성 관리를 위한 준비 & 실행 단계


  • security Group 생성


  • Key Pair 생성



  • Floating IP 생성



  • VM Image 확인


  • 생성된 VM 목록




12. LBaas(LB As A Service) 의 설정과 테스트


LBaas 의 설치 방법에 대해서는 여기를 주로 많이 참조하게 되는데(Kilo 버전 기준), 현재 시점의 Mitaka에 적용시에 적욥하지 않은 일부 오류가 숨어 있으므로 아래의 내용으로 진행하면 크게 무리 없이 성공적으로 수행 가능



  • Controller, Network 노드에서 각각 haproxy, lbaas 설치
[root@openstack-ctrl ~]# yum install -y haproxy openstack-neutron-lbaas


  • Controller 노드에 driver, plugin 설정
[root@openstack-ctrl ~]# vi /etc/neutron/neutron_lbaas.conf
service_provider = LOADBALANCER:Haproxy:neutron_lbaas.services.loadbalancer.drivers.haproxy.plugin_driver.HaproxyOnHostPluginDriver:default
[root@openstack-ctrl ~]# vi /etc/neutron/neutron.conf
service_plugins 리스트에 lbaas 추가
[root@openstack-ctrl ~]# systemctl restart neutron-server.service

  • Network 노드에 lbaas agent 설정
[root@openstack-net ~]# vi /etc/neutron/lbaas_agent.ini
device_driver = neutron.services.loadbalancer.drivers.haproxy.namespace_driver.HaproxyNSDriver
user_group = haproxy
interface_driver = neutron.agent.linux.interface.OVSInterfaceDriver

[root@openstack-net ~]# systemctl restart neutron-lbaas-agent.service
[root@openstack-net ~]# systemctl enable neutron-lbaas-agent.service

  • Horizon(Controller 노드) 설정
[root@openstack-ctrl ~]# vi /etc/openstack-dashboard/local_settings
'enable_lb': True

[root@openstack-ctrl ~]# systemctl restart httpd.service


이제 Openstack Horizon 에 로그인하여 Network 패널 아래에 Load Balancer 라는 서브 패널이 나타나며, Load balancer 설정 및 사용이 가능해 짐을 볼 수 있다.


다음에 다룰 내용은?


2016년 10월에 Mitaka 후속 버전으로 Newton 버전에 릴리즈되었습니다. 다음에 다룰 내용은 최신의 Newton 버전이 되겠으며, 본 포스팅과 중복되는 부분은 제외하고, 기본적인 설치/설정 과정의 요약 정리와 함께 로드밸런싱을 위한 VM 이미지의 준비, Haproxy 기술을 활용한 LB의 대상이 되는 VM에 웹서버를 설정하고 부하 분산의 작동 테스트를 포함하여, answer 파일의 개별설정과 VM의 HOT(Heat Orchestration Template)에 의한 VM 자동증설(Auto-scale: Scale out/in)에 대해서 다루어 보도록 하겠습니다. 


- Barracuda -


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

Barracuda

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


2016년 3월 9일, 이세돌은 알파고와 ‘구글 딥마인드 챌린지 매치’ 5번기 제1국에서 186수 만에 흑으로 불계패를 당했다.  한마디로 충격적이다. 세계 최정상급 프로바둑 최정상급 기사 이세돌의 패배를 어떻게 받아들여야 할까?


약 십 수년 년 전에 필자는 "The Many Faces Of Go" 라는 PC용 바둑프로그램을 어렵사리 구해서 바둑을 두어 본 적이 있었다. 지금처럼  온라인 웹사이트도 없었고 관련 정보도 부족했지만 아마추어 4~7급 수준이라고 알려졌었고, 당시 필자의 수준은 대략 7~9급 수준이었다고 기억된다. 하지만 당시 나는 한마디로 프로그램을 "가지고 놀았"었다.


원래 하수들은 되지도 않은 꼼수를 좋아한다. 내 꼼수에 컴퓨터 프로그램은 아무렇게나(녀석은 나름 최선을 다했겠지만) 맞두다가 박살이 났었으니까. 심지어 초읽기 시간제한도 최대로 했었는데 말이다. 그래서 "아직은 아니구나" 생각했던 시절이 기억난다. 그런데..., 이런 일이 결국 일어나고 말았다. 경우의 수가 상대적으로 작은 장기나 체스도 아니고 바둑에서 말이다.


컴퓨터에 사람이 진 것이 처음은 아니다. 그러나...


지난 1997년 체스 챔피언 카스파로프를 무너뜨린 IBM의 딥블루는 일종의 버그[각주:1]로 인간을 물리쳤다는 말(연합뉴스 참조. Deep Blue 개발자의 고백)이 있다. 한편, 프로 바둑기사가 컴퓨터에 패배한 것이 처음은 아니다. 이미 2015년 10월에 알파고는 유럽챔피언 판후이 2단를 5대 0으로 대파한 적이 있었다. 


2015년 10월 판후이 2단: 알파고 기보 중 하나


이때만 해도, 바둑의 본고장도 아니고 유럽의 프로 2단 실력이니 그럴 수도 있겠다 싶었다. 그러나 세계 최정상급 이세돌 9단이 근소하게 진 것도 아니고 186수 만에 불계패를 당했다. 이제 바둑에서까지 사람이 컴퓨터에 도전해야 하는 시대가 온 것인가?



'알파고'의 수준은 어느 정도인가?


알파고는, 타이젬[각주:2] 기준으로 20초 속기 수준으로 보면 프로 50위권 밖이라고 평가되었었다(관련 기사). 하지만 이 평가는 알파고가 최정상급 프로기사들의 기보를 집중적으로 공부하기 전의 수준이 아니었을까 짐작된다. 알파고는 판후이와 대국하기 전에 유럽의 아마추어 대국 기보를 공부했었다고 한다(이러고도 판후이에게 압승). 그리고 그 이후에는 세계 최정상급 기사들의 기보를 집중적으로 연구했었을 것이라는 추측이다. 


알파고의 능력을 보자면, 이미 3000만 개 이상의 기보를 학습했다고 하며, 당연한 말이겠지만 쉬지도 자지도 않고 대국을 하며 바둑 수를 배웠다고 한다. 벌써 2016년 1월 동안 4주 만에 100만 대국을 익혔고 하루에 3만 대국을 소화할 수 있다고 딥마인드 측은 밝힌 바 있다(참고: 알파고 알고리즘 요약 - slideshare.net).


2016년 3월 9일 이세돌:알파고 제1국 기보. 타이젬 기보 캡처


대국 전 이세돌 기사는 호언장담했었다. 2시간 제한에 60초 초읽기 3번 정도면 5:0 내지 4:1 정도로 이기지 않을까 생각한다고..., 그리고 세계 최정상급 프로기사들의 기보는 최대 1만 5천 개 가량밖에 안 되고 저작권 문제도 있으므로 제아무리 최강의 "딥 러닝(Deep Learning)과 강화학습(Reinforcement Learning; RLAI)" 능력을 가진 알파고라도 사람에게는 무리라는 예측이 나돌았었다. 그러나 첫 대국이기는 하지만 그 결과는 참담하다. 


결국, 불계로 끝났지만, 형세 상으로 보면 덤 7집 반을 공제하고 반면으로 4집 반가량 알파고가 앞섰고, 프로 기사의 대국에서 4집 반이면 아마추어에서는 소위 '만방' 이라는 점이 더 무섭게 다가온다. 일반 뉴스나 신문들은 우세한 국면에서 이세돌이 실수 했기 때문이라고 하지만, 일부 전문가들은 대국 내내 단 한 번도 이세돌이 우세했던 적이 없었다고 분석하기도 한다(나무위키).



그러나 결국 인간이 승리할 것?!


총 5국 중 제 1국을 승리한 DeepMind 측 CEO인 데미스 하사비스(Demis Hassabis)는 뛸 듯이 기뻐하며 "우리는 달에 착륙했다." 라고 소감을 밝혔다고 한다. 한 편, 대국을 끝낸 알파고는 담담하게 다음과 같은 소감을 밝혔다고 전해진다. "0001011010111110101000010111101010...". 믿거나 말거나.


IT 관계자로서는 박수를 보내고 싶긴 하지만 ..., 어찌 보면 섬뜩한 결과에 무서운 생각마저 들기도 한다. 이미 인공지능으로 무장한 컴퓨터에 회계사, 변호사, 각종 자문역할 등의 전문직 일자리까지 뺏길지도 모른다는 불안감이 팽배한 이 시점에서 말이다. 그러나, 냉정하고 실수가 적은 '컴퓨터'라는 상대방을 몰라서 방심한 한순간의 실수일 뿐, 결국 바둑에서만큼은 인간이 승리할 것이라고 믿고 싶다.


이 대국 결과를 두고  에릭 슈미트 구글 회장은 "대국의 결과와 상관없이 최종 승자는 인류라"는 의미심장한 말을 남겼다. 나는 이 말이 진심이라고 믿고 싶다. 사람은 컴퓨터를 지배하고 통제하면서 활용할 수 있어야 한다. 그렇지 않다면 언젠가 영화 '터미네이터' 와 같은 암담한 종말이 올지도 모르기 때문이다.



번외1: 2국도 졌다, 이 난국을 어떻게 하나?


1국에 이어 3월 10일에 속행된 2국도 완패했다. 알파고의 "신의 한 수" 인 37수는 인간의 고정관념의 정곡을 찔러버린 수라고들 한다. 알파고가 1분 만에 한 수씩 둔다는 생각도 깨졌다. 국면이 복잡해지자 2분가량 소위 '장고'를 하는 모습도 보여 줬기 때문이다.


"생각외로 만만치 않다"가 아니라 이제 한 판이라도 이겨야겠다는 인간 이세돌의 절박함은 다음과 같은 대응책을 밤새 고안해 냈다. 그의 절친 홍민표 9단, 박정상 9단 등과 함께...(경향신문).


그런데 이 와중에 이세돌이 졌기 때문에 바둑의 부흥을 위해 새누리에 입당했다는 조훈현 씨는 도대체 무슨 생각일까? 그게 무슨 상관이 있는 건지 참으로 오묘신기 빵빵이다. 어쨌든...,


  • 알파고는 수많은 계산이 필요한 초중반에, 변수(경우의 수에 의한 변화)를 줄이려고 노력한다. 즉 오묘한 형세 변화를 주도하는 '패'를 유도하여 반상의 변화를 꾀한다(이 부분은 대국 후 이 9단과 함께 작전회의(?) 를 했던 동료 기사들의 주문이었고, 정작 이 9단 본인은 "자기의 바둑을 두면 되지!"라고 결연히 말했다는 후문이다 = 노컷뉴스)).
  • 마찬가지 전략에 의해, 알파고가 싫어하는 듯한 국면 즉, 판의 모양을 크게 만들어야 한다
  • 당연한 이야기지만, "실수 줄이기"이다. 상대방도 완벽한 인공지능은 아니므로 초중반에 알파고가 저지르는 실수를 파고들면서, 경우의 수가 줄어드는 후반에 실수를 줄여야 승산이 있다(사실 알파고의 37수는 일종의 실수가 아닐까 하는 생각이 드는 건, 내가 하수이기 때문일까? ^^;;).


과연 결과가 어떻게 될지 귀추가 주목되는 "진정한 세기의 대결"이다. 이세돌 파이팅!!!



번외2: 3국도 완패, 그런데 커제라면?


3국도 걱정했던 대로 완패하고 말았다. 이제 5국 중에서 단 한 판이라도 이겨 보는 게 목표로 바뀔 듯하다. 패에 의한 복잡한 양상은 피하는 듯했지만, 팻감을 염두에 둔 형세 판단에 의한 선택도 정확했다는 평이 지배적인 듯하다.


이제 본격적으로 인간이 인공지능 컴퓨터에 도전하는 형국이 되는 것이다. 돌아가는 분위기상, 최근 이세돌과 호각을 보이는 중국의 커제 9단이 알파고의 도전을 받아들일 듯한데, 그 결과가 주목되는 시점이다.


한편, 국내 뉴스들이 전한 바 있는 커제의 '망언'은 중국어에 일가견이 있는 한 네티즌에 의하면 오보라는 얘기가 있다. 다시 한 번 국내 기자들의 기레기적 속성이 고개를 내민 것이 아닌가 하는 생각을 지울 수가 없다(MLBPARK). 번역된 내용을 보면 커제가 했던 말 어디에도 "이세돌이 인류 대표 자격이 없다." 라는 말은 찾아볼 수가 없었다는 것이다.


사실 확인 없이 퍼 나르기만 하는 "기레기는 각성하라!" 제발~!!!



번외3: 이세돌의 첫 승리를 축하합니다


마음을 비운 듯한 이세돌이 드디어 최강의 인공지능 알파고(알사범, 알9단으로도 불리운다고...)에게 첫 승리를 거두었다. 이 9단의 힘겨운 승리를 지켜보며 2014년에 개봉했던 영화 "신의 한 수" 의 내용이 잠깐 떠올랐다. 주님(안성기 분)이 죽어가면서 태석(정우성 분)에게 한 말이다. "너무 유연해서 꺾을 수가 없어. 부러지지가 않아... 아이가 두는 거야, 순수한..."



영화 "신의 한수(2014, 조범구 감독)" 중에서



어쨌든, 이로써 인공지능도 상대방의 신의 한 수에 멘붕(?)을 겪고 '떡수' 를 두거나 치명적인 실수를 할 수도 있다는 것과, 알파고도 불계패 즉, "돌을 던질[각주:3]" 수도 있다는 것을 보여 주었다. 집념의 이세돌, 정신력과 집념의 승리를 축하합니다. 바둑계의 동료 기사들도 "아름다운 바둑", "멋진 대국"이라고 칭찬했다고 한다.



이세돌의 회심의 일격. 사진 출처: Yettie Studio 생중계 화면 캡처




AlphaGo, 돌을 던지다! 사진 출처: Yetti Studio 생중계 화면 캡처



2016년 3월 13일 이세돌:알파고 제 4국 기보. 타이젬 기보 캡처



- Barracuda -


  1. 더 이상 불리한 국면을 타개할 방법이 없자 랜덤하게 아무렇게나 두어 버리는 일종의 버그수. 정작 상대방인 카스파로프는 도대체 이게 뭔가 하고 깊은 고민을 하다 멘탈이 무너져서 악수를 두고 이후 게임들까지 망쳐버렸다는 후문 [본문으로]
  2. 타이젬바둑(Tygem)은, 동양온라인이 운영하고 있는 인터넷 바둑사이트. 2004년에 설립되었고 한국, 일본, 중국 각지에 홈페이지가 있다. 대국 시스템 이외에, 기전, 바둑강좌, 각종 이벤트를 운영하고 있다. 2004년에 조훈현, 이창호, 유창혁등의 바둑기사, 동양그룹, 중앙일보, DACOM 등에 의해 설립되었다. 2008년 7월에는 홈페이지 명칭이「동양바둑」이라 개칭되었다(위키백과) [본문으로]
  3. 전해지는 바로는 승리할 확률이 10% 이내이면 항복 즉, 돌을 던지게 되어 있다고 한다. [본문으로]
저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

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

Mac OSX 에는 Xcode 라는 좋은 무료 개발도구가 있다. 물론 Mac 에서도 CodeLite, CodeBlocks 와 같은 잘 알려진 오픈소스 개발도구를 쓸 수 있지만, Codeblocks 는 Mac OSX 에서의 안정성이 떨어지는 감이 있고 CodeLite 는 Mac 에서 command-line 프로그램 개발 시 iostream 의 cin 을 제대로 처리 못하는 등 조금씩 문제를 안고 있기 때문에 쓰다 보면 불편함이 쌓이게 되고, 그러다 보니 Apple 에서 Mac 을 가장 잘 지원해 주(는 것으로 믿고 싶은...)는 Xcode 같은 Native app으로 다시 돌아가곤 한다.


이번 글에서는 Xcode 로 C++, Objective-C, Swift 와 같은 언어를 사용한 개발에 있어서, 협업 개발을 위한 git 저장소와 Xcode 가 자체적으로 제공하는 git client 기능을 사용하는 방법을 기록해 두려 한다.


개발자의 결벽증

물론 Remote 저장소에서 repository를 cloning 하거나 Local 저장소에서 작업하다가 그때 그때 push 하면서 "미친 년 널 뛰듯이" 아무렇게나 되는대로 작업할 수는 있다. 그러나 사람마다 기준이나 스타일은 다르겠지만, 하드디스크의 작업 영역에 온갖 너저분한 임시 checkout 디렉토리, 수정하다 만듯한 폴더와 파일 조각들이 여기저기 널려 있는 꼴은, 마치 소스의 indent 가 들쭉날쭉한 참상을 보는 것 만큼이나 속상하는 일이기 때문에 다음과 같은 원칙을 정해 둔다


  • 하나의 프로젝트에 대해 주로 작업하는 Local 저장소와 Remote 저장소의 대응은 1:1 일 것
  • 사용하는 Tool 이 Sublime Text 또는 Sourcetree 이건 Xcode 이건 간에 프로그램 소스가 저장되는 repository는 하나만 존재할 것
  • 프로그램 소스코드와 데이터파일을 제외한 프로젝트파일, 바이너리 실행 또는 오브젝트 파일들은 git 을 통한 버전관리에서 제외 될 것


필요한 것들(또는 지금 쓰고 있는 것들)

  • Remote 저장소(Github, Gitlab 계정 또는 본인만의 git server) - 필자는 Remote git 저장소를 위한 AWS 가상머신(doubleshot.io)
  • Git Client - OSX 에 내장된 자체 git & Sourcetree app



정리해 두려는 것들

  • 이미 존재하는 Remote 저장소를 내려 받고 Xcode 에서 작업하거나 또는,
  • 새로운 Local 저장소를 만들어 Xcode 에서 작업하고, Remote 저장소와 연동, 업로드(보관)



1. Remote 저장소를 내려 받고 Xcode 에서 작업하기


1.1 저장소 이름은 gittest2, Remote 저장소의 주소는 ssh://gituser@doubleshot.io/home/gituser/repos/gittest2 라 하자. 


1.2 가장 먼저, Sourcetree 에서 Remote 저장소를 cloning 해서 Local 로 다운로드 한다. 다음으로 Xcode 에서 작업할 새로운 프로젝트를 생성해 두고, Sourcetree 창을 나란히 배치한다.


해당 repository 에서 R-click, "Show in Finder" click


Finder 에서 저장소 폴더를 선택, Xcode 의 프로젝트 내부로 drag 한다(또는 Finder를 거치지 않고 Sourcetree 에서 Xcode 로 직접 drag 해도 된다)


Xcode 는 외부 폴더를 가져올 때의 옵션을 묻는다. 그림과 같이 설정하고 Finish!(Create Group을 하면 Xcode 내에 그룹이 만들어지고 그 내부에 git 폴더가 논리적으로 참조되고, Create Folder Reference 를 하면 프로젝트 내에 그룹이 만들어지지 않고 git 폴더 자체가 직접 참조된다)


Xcode 프로젝트 안으로 외부 git 저장소가 안전하게 import 되어 있다. 오른쪽 정보창에서 저장된 path 를 확인해 보자. Xcode 가 기본으로 자동 생성해 주는 main.cpp 는 휴지통으로...!



1.3 위의 과정을 거치면 Xcode 내에서 add, commit, push 등 모든 git 기능을 사용할 수 있다. Xcode 프로젝트 내에 Working copy 가 복사되어 만들어지는 것이 아니라, Sourcetree 가 Clone 해 둔 repository를 그대로 사용하는 것이다.



2. Local 저장소로 Xcode 에서 작업하고 Remote 저장소와 연동하기


2.1 Git server 에서 Remote 저장소를 생성해 둔다(이 과정은 아래 2.7 이후에 해도 된다). 저장소 이름은 xcodegit_repo, Remote 저장소의 주소는 ssh://gituser@doubleshot.io/home/gituser/repos/xcodegit_repo 라 하자. 


2.2 Sourcetree 에서 "Create a local repo..." 선택, Local 저장소를 생성한다(이 때, "Also create remote..." Check는 반드시 해제).



2.3 Xcode 에서 작업할 새로운 프로젝트를 생성한다.



Xcode 에서 프로젝트 생성 후, 자동 생성되는 main.cpp 는 휴지통으로... 만약 사용할 필요가 없다면 main.cpp 파일이 속한 그룹 전체를 삭제해도 무방함



2.5 Sourcetree 에서 Local 저장소를 R-click, "Show in finder" 선택. Finder 에서 Xcode 프로젝트 내부로 폴더 drag

Xcode 의 프로젝트 내부로 drag(Sourcetree 에서 직접 drag 해도 된다)


1.2와 마찬가지로 가져오기 옵션 설정



2.6 Xcode 로 가져온 Local 저장소 폴더 R-click, "New file" 을 선택(저장되는 path 확인), 작업할 소스 파일(cpp 등) 생성.



2.7 Xcode 에서 소스 파일을 편집, 저장 후 git add, commit 등 실행(로컬 저장소에 저장)


2.8 작업이 완료된 소스 파일을 Remote 저장소와 연동 저장하기 위해서는 소스 파일이 저장된 폴더 선택, Xcode 메뉴의 "Source Control" 선택, Remote 저장소 정보를 설정한다.


Add Remote 선택


Git server 에서 생성해 둔 Remote 저장소의 주소를 등록


코딩 및 테스트 작업이 끝나면 Xcode 메뉴에서 "Source Control" -> Push 를 통해 Remote 저장소에 업로드할 수 있다


<참고> Git server 와의 연결을 ssh 프로토콜을 통할 경우에는, Xcode 가 생성해 주는 ssh 인증키를 git server 측과 '키교환'을 해 두어야 한다  


- Barracuda -


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

Barracuda

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


소장하고 싶은 영화나 동영상 파일들을 모아서 디스크 한 켠에 폴더별로 잘 정리해서 보관하는 경우가 많다. 그렇다고 자주 들춰보기는 쉽지 않지만, 일종의 "혹시나 다음에 또 보고 싶어질지도 몰라...ㅋ" 하는 미련 같은 건 아닐까(그러다 결국 한 순간에 폴더째 지워버리게 되기도 하지만...ㅠㅠ). 아, 위에 나오는 짤방성 이미지의 context sensitive한 자막은 참 공감이 많이 가고, 무엇보다 푸근한 처자에게 고맙다(뭐가 ? ^^;;).


2016년 첫 포스팅 치고는 거의 솔플메모에 가까울 정도로 가볍다. 언제나 심각하고 거창할 순 없는 거다. 각설하고, 이제 본론인 메모 작성으로 넘어가자.


Handbrake로 동영상을 변환

다운로드 받은 영화를 보관 또는 거실TV에서 상영하기 전에 거실 PC의 Linux(Ubuntu 14.04 LTS)에서 무료 동영상변환기인 핸드브레이크(Handrake)로 인코딩 하게 된다(Mac book에도 Adapter라는 쓸만한 인코딩 앱이 있긴 하지만, 귀여운 내 맥프레에게 이런 격 떨어지는 일을 시키고 싶지는 않다).


그런데, TV(스마트 TV가 나오기 전 모델의 42인치 LG LED)의 미디어플레이어가 개후져서 조금 옛날 것이라 AC3 오디오를 인식 못하거나 새로운 코덱으로 인코딩 된 mp4 파일은 아예 화면도 띄우지 못하는 경우가 많다. 이 때 만날 수 있는 여러 문제 상황을 단순하게 넘기려면, 아예 Handbrake에서 동영상 변환을 할 때 영상의 화면에 자막을 심어 버리는 작업을 하는 쪽이 속 편하다.


한글 자막 .smi 파일은 대부분 EUC-KR 코드

인터넷에서 입수되는 대부분의 한글 자막은 smi 포맷으로 되어 있고, 한글코드는 EUC-KR 인 경우가 많다(불평은 하지 않습니다. 자막 제작자님들 고맙습니다 ^^). 그런데 문제는 Handbrake가 srt 포맷의 자막만 지원하기 때문에 2번의 변환을 거쳐야 한다. 한 번은 한글 코드 변환, 또 한 번은 자막파일 변환. 이과정이 귀찮아서 Bash shell 을 작성해 보았다. 사용 방법과 소스는 아래 참고.


자막 변환 스크립트 사용법

우선 smi -> srt 자막을 변환하는 Linux 용 프로그램 subs를 설치해야 한다(작성자 <Dmitry Karasik, dmitry@karasik.eu.org> 에게 감사). 좀 오래 된 거지만 지금도 쌩쌩 잘 돌아간다.  그 다음에 사용할 iconv 는 대다수 Linux 배포본에 포함된 코드 변환 도구이므로, 따로 설명은 생략한다.


다음과 같이 subs 를 우선 설치.

$ sudo apt-get install libsubtitles-perl

$ subs -> 테스트 삼아 한 번 실행해 보자

subs: convert, join, split, and re-time subtitles


format:

   subs [options] subfile [ subfile ... ]


options:

   -a coeff, - a and b coefficients in linear transformation

   -b time     u=at+b, where t and u are src and dest times

...


사용법은 간단하다. 스크립트(conv_eucsmi2utfsrt.sh ... 파일 명은 알아서 변경)를 Linux 계정의 $HOME/bin  정도에 복사해 두고(chmod +x 가 필요할지도...), 변환할 자막 파일이 있는 경로로 가서 "conv_eucsmi2utfsrt.sh 자막파일.smi" 같은 식으로 실행해 주면 된다. 변환에 성공했을 경우 다음과 같이 나오게 될 것이다.


명령어 인자로 전달하는 자막파일명 부분에 와일드카드를 쓴다든지, 여러개 파일의 일괄변환 등에 대해서는 따로 고려하지 않았다. 모든 smi 파일을 단숨에 변환할 일이 그다지 많지 않기 때문인데, 나중에 필요하면 한 번 생각해 보기로...


$ conv_eucsmi2utfsrt.sh tmp.smi

Converted tmp.smi(euc-kr) to tmp.srt(utf8)

Enjoy it! ;)


특별한 기술이 필요하거나 대단히 복잡한 것은 없다. 잘 보면 아래 부분 실행문들에서 iconv, subs, sed 를 적당한 옵션을 주어 각각 실행하는 것이 전부다. 그러나 귀찮은 작업을 스크립트 하나 실행해서 해 낼 수 있다는 점에서 자화자찬 중.


conv_eucsmi2utfsrt.sh (Bash shell script 소스)

#!/usr/bin/env bash
# Bryan(Barracuda), 2016.1.
# Usage: conv_eucsmi2utfsrt.sh filename.smi(with EUC-KR Kor. char.)
#
# *.smi 파일(euc-kr)을 iconv와 subs 를 써서 *.srt(utf8) 형식으로 변환
# 변환 후 srt 파일 내에 smi 파일에서 자주 보이는 흔적인   가 있으면 삭제함
# 자막 변환 도구 subs는 sudo apt-get install libsubtitles-perl 로 설치되어 있어야 함(ubuntu 기준)

args=1
param=$1

if [ "$#" -ne "$args" ]; then
    # $0 의 Path 부분 삭제: ${0##/*/}
    echo -e "\033[0;33m$# arguments ... use \"${0##/*/} filename.smi\"\033[0m"
    exit 1
fi

# 인자로 전달 된 파일명에서 확장자 부분을 제외한 앞 부분을 잘라냄(뒤에서부터 .로 시작하는 부분을 삭제)
# param이 . 으로 끝나면 basename은 "filename", extension은 ""
# param에 . 이 없으면 basename은 "filename", extension도 "filename", index는 "0"
basename=${param%*.*}
extension=${param##*.}
index=`expr index $param .`

# 파일명이 . 으로 끝나는 경우, 파일명에 . 이 없는 경우, 확장자가 smi 가 아닌 경우 오류
if [[ "$extension" = "" || "$index" = "0" || "$extension" != "smi" ]]; then
    echo -e "\033[0;33mBad file extension ... I want \"*.smi\" file.\033[0m"
    exit 1
else if [ ! -f $param ]; then
        echo -e "\033[0;33mFile $param missing.\033[0m"
        exit 1
    fi
fi

if [ -e "$basename.srt" ]; then
    echo -e "\033[0;33mFile $basename.srt already exists. Plz delete or rename it.\033[0m"
    exit 1
fi

# 한글 euc-kr 코드를 utf8 코드로 변환
iconv -f euc-kr -t utf8 $basename.smi > _$basename.smi
# smi 파일을 srt 파일로 변환
subs -c srt _$basename.smi -o _$basename.srt
# srt 파일 내의   를 제거
sed "s/&nbsp;//g" _$basename.srt > $basename.srt
# 중간 작업파일 제거
rm -f _$basename.smi _$basename.srt

echo -e "\033[0;32mConverted $basename.smi(euc-kr) to $basename.srt(utf8)"
echo -e "\033[0;32mEnjoy it! ;)\033[0m"


Mac 사용자라면 간단히 MinySubtitleConverter 를 써 보자


만약 Linux 가 아닌 Windows 나 Mac 사용자라면 http://blog.myhyuny.com 블로그를 방문하여 MinySubtitleConverter 를 다운로드 받아서 사용하면 위의 과정을 간편하게 한 번에 끝낼 수 있다. 직접 테스트 해보니 한글코드와 srt 변환이 아주 훌륭하게 작동한다.


2014년 3월이 마지막 업데이트이긴 하지만 OSX 10.11(El Capitan) 에서도 문제 없이 사용할 수 있는 것으로 확인되었다.




Input Encoding 을 따로 지정하지 않고 Auto 로 두어도 한글 코드를 자동으로 인식한다. File 메뉴에서 Open -> smi 파일을 선택하면 smi 파일이 있는 폴더에 srt 파일이 자동 생성된다.



- Barracuda -


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

Barracuda

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



[스택오버플로우] 2줄 짜리 단순한 질문에 대한 명료한 분석과 이유를 달아준 명답 of 명답. 지난 5월에 올라온 이슈답변에 이 시간 현재 평점 38만 점, 황금배지 54개!

보러 가기


짧고 격하게 공감하고 오래 기억하라


단순한 따라하기 보다는. 왜 그래야 하는지 알려고 노력하는 것이 얼마나 중요한지 새삼 깨닫게 해 준다.

비단 C++코딩에서 뿐이랴. 특히 과학을 하는 이에게 원리와 이유의 탐구가 얼마나 중요한가 말이다. 그러니 늘, 기본에 충실하라. 이건 나 자신에게 하는 말.


* RAII 는 Resource Acquisition IS Initialization 이라는 표현의 약어로 C++을 창시한 Bjarne Stroustrup 이 주장하는 일종의 기술적 원칙이다.


[스택오버플로우] 2줄 짜리 단순한 질문에 대한 명료한 분석과 이유를 달아준 명답.지난 5월에 올라온 이슈답변에 이 시간 현재 평점 38만 점, 황금배지 54개!단순한 따라하기 보다는. 왜 그래야 하는지 알려고...

Posted by Bryan Lee on Wednesday, December 2, 2015


- Barracuda -


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

Barracuda

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



지난 포스팅의 마지막에 Fizzbuzz 를 풀어 내는 희한한 예제를 게시한 바 있습니다. 좀 오래 되긴 했지만, 궁금해 하는 후배가 있어 한 번 같이 분석해 보았고 독특하고 엉뚱한 생각에 재미를 조금 느끼기도 했습니다.


실제 인터뷰시의 사례인지는 알 수 없지만, 이 해법을 소개한 페이지(Fizzbuzz 에 지겨워진 개발자들)을 잠깐 보면, "일단 똑똑하다", "뽑고 싶다" 거나 "생각이 한쪽으로 쏠린 사람", "팀웍을 해칠 것 같다" 는 등의 다양한 반응를 예로 들고 있네요. 글 쓴이(Samuel Tardieu) 자신은 일을 하면서 이런 식의 재미를 추구하는 방식을 좋아한다고 적고 있기도 합니다. 여러분은 어떠신가요?


우선, 소개된 원본 소스를 그대로 두고 한 번 훑어 보기로 합니다. 참, 한 번 실행시켜 보셨나요? 다른 잘 짜여진 소스와 마찬가지로 정확하게 결과를 출력해주고 있습니다. 희한하네? ^^;;

#include <stdio.h>

static const char *t[] = {"%d\n", "Fizz\n", "Buzz\n", "FizzBuzz\n"};

int main()
{
  unsigned int i;
  for(i = 1; i <= 100; i++) printf(t[3&19142723>>2*i%30], i);
  return 0;
}


C 프로그램이고, t 라는 문자열들을 초기화해서 가지고 있는 문자열 포인터 배열이 보입니다. t[0] 는 "%d\n", t[1] 은 "Fizz\n" ...이 되겠지요.


그 외에는 for loop 에서 printf 함수를 쓴게 답니다. 그럼 어디에서 저런 괴물같은 결과가 나온 걸까요?


가만히 보면 printf 에서 사용하는 파라미터의 t[3 & 19142723 >> 2 * i % 30] 부분에 뭔가 비밀이 숨어 있다는 얘긴데요. [] 내부의 값은 t[] 배열의 index 로 0~3 사이의 정수값을 가지게 되겠네요.


(1) 어떤 트릭일지 들여다봅시다. 3 & 19142723 >> 2 * i % 30 ... 부분을 보면 우리 눈을 현혹시키는 것이 한 가지 숨어 있습니다. 다름 아닌 연산자 우선순위! 이게 첫번째 포인트.


저 계산식을 우선 연산자 우선순위에 맞게 괄호를 써서 어떤 값을 어떻게 가지게 되는지 확인해 보아야겠습니다.


3 & (19142723 >> ( (2 * i) % 30) ) 이렇게 됩니다. &는 bit AND 연산으로 가장 우선순위가 낮고, 그다음 낮은 것이 >> 즉 RShift(오른 쪽 비트와이즈 쉬프트 연산) 이지요.


자 그러면 (2*i) 가 가장 먼저 계산됩니다. i는 1~100 사이의 카운터니까, (카운터 * 2) 를 계산한 값을 30으로 나눈 나머지를 취하는군요. 즉 2%30, 4%30, 6%30, ..., 28%30, 30%30 처럼 {2, 4, 6, ..., 28, 30, 0, 2, 4, ....} 와 같이 나열해 보면 15를 주기로 0부터 리셋되어 2씩 값이 증가하는 수들이 나열됩니다.


(2) 그럼 위의 식은 19142723 이라는 묘한 숫자값을 처음에는 2회 Rshift, 그 다음 4회 Rshift, 이런 식으로 반복해서 2칸씩 Rshift 하고 3(2진수로 11)과 bit AND 연산을 하는 겁니다. 그렇다면...


(3) 모든 비밀은 19142723 이라는 숫자에 숨어 있는 거군요. 이 숫자를 2진수로 출력해 보면(아래 예제에 2진수 출력 함수가 있으니 확인해보세요) 00 00 01 00 10 01 00 00 01 10 00 01 00 00 11 이렇게 됩니다. 이해하기 쉽게 2자리씩 끊어서 보자구요.


위의 (2)에서처럼 생각하면, 카운터 i 가 1일 때 00 00 01 00 10 01 00 00 01 10 00 01 00 00 11 을 오른쪽으로 2칸 밀면 끝 자리에 00이 나오고 2진수 11과 AND 연산을 하면 00 즉 0이 나옵니다. 카운터 i가 2일 때는 오른쪽으로 4칸 밀면 끝자리에 00, 2진수 11과 AND 연산을 하면 00, 카운터 i 가 3일때는 6칸 밀고 끝자리에 01 과 2진수 11 AND 연산하면 01 이 나오지요.


즉 주어진 숫자를 2번씩 Rshift 하면서 2비트씩 뽑아 먹는 일을 계속 반복하는 겁니다.


자, 여기서 다른 Correct한 예제 솔루션들에서 출력되는 결과를 다시 한 번 보면,


 1

 i 값 = t[0]

 2

 i 값 = t[0]

 Fizz

 t[1]

 4

 i 값 = t[0]

 buzz

 t[2]

 Fizz

 t[1] 

 7

 i 값 = t[0]

 8

 i 값 = t[0]

 ...

 

 15

 t[3]

 16

 i 값 = t[0]


이 패턴이 15개씩을 주기로 반복되고 있습니다(당연하겠지요 ^^;; 아마도 짖궂은 코더는 여기서 아이디어를 착안했겠지요).


이제, 위 표 오른쪽 칼럼의 t[]의 인덱스를 보니 {0, 0, 1, 0, 2, 1, 0, ...} 이렇게 되는데, 2자리의 2진수로 바꿔서 다시 나열해 보면 {00, 00, 01, 00, 10, 01, 00, ...} 이렇게 됩니다.


위의 (3)에서 보이는 2진수를, 오른쪽에서부터 11를 제외하고 숫자를 2개씩 묶어서 나열해 보면 정확히 맞아 떨어지지요.


이제 감이 조금씩 잡히시나요? 매직넘버로 보였던 19142723 이라는 숫자가, 사실은 미리 관찰된 패턴에 의해 나오는 t[]의 인덱스 값을 오른쪽(가장 낮은 값에는 11을 채우고) 에서부터 왼쪽으로 채워나가서 만들어지는 2진수를 10진수로 표현한 값이라는 얘기입니다. 한 마디로 줄이면, 솔루션이 아니라 '사기' 입니다.


이해를 돕기 위해 아래 C++ 프로그램을 작동시켜 보시기 바랍니다. 말로 설명하는 것보다 프로그램을 보고 실행해 보는 것이 더 직관적으로 이해가 잘 될 수 있을 것 같습니다.

#include <iostream>

using std::cout;
using std::endl;

void fizzbuzz7(int _n)
{
    static const char *t[] = {"%d\n", "Fizz\n", "Buzz\n", "FizzBuzz\n"};

    for(int i = 1 ; i <= _n; i++)
        printf(t[3 & (19142723 >> ((2 * i)%30))], i);
}

// 10진수를 2진수 값으로 출력
void binary_cout(int _n)
{
    if( _n <= 0)
        return;

    binary_cout(_n >> 1);

    cout << _n % 2;
}

int main()
{
    // Magic number 19142723는 2진수로 00 00 01 00 10 01 00 00 01 10 00 01 00 00 11 (2자리씩 끊어서 표기)
    int x, y;

    // fizzbuzz7 과 같은 로직에 해설을 추가하여 다시 써 봅니다
    for(int i = 1; i <= 100; i++) {
        cout << i << ": Magicnumber 를 " << (x = (2 * i) % 30) << "회 Rshift -> ";
        binary_cout(y = (19142723 >> x));
        cout << " 이 되고 이것을 0x11 과 bit and를 하면: " << (3 & y) << " 이 되며, 이것은 배열의 index." << endl;
    }

    return 0;
}



누가 작성했는지 모르지만 이 프로그램의 코더는 분명 일반인들의 허를 찌르는 센스를 가진 사람일 것 같습니다. 틀에 얽매이지 않는 자유로운 영혼, 저도 이런 분들을 좋아하고 또 본받으려고 노력합니다. 물론 프로그래머로서의 목표의식과 인성이 갖추어져 있다는 전제하에서.


발칙하다는 것은 기발하고 독창적이라는 말과 비슷합니다. 하지만 또 어떻게 보면 사회성이 떨어지고 규범을 지키지 않을 것 같은 부정적인 느낌을 주기도 합니다. 위의 솔루션은 사실 예상되는 답의 패턴에 문제해결 과정을 끼워 맞춘 것으로, 엄밀하게 말하자면 문제해결에 적합한 코딩은 아닙니다. 즉 나누는 수를 3과 5에다가 새로운 수인 7을 하나 더한다든지 하게 되면, 예상되는 결과로부터 패턴을 추출하여 프로그램 내의 매직넘버를 다시 계산하고 t[]의 index 를 계산하는 식도 다시 써야 하니까요.


또 이 솔루션은 문제를 출제한 사람을 사실은 조롱하고 있다고 생각할 수 있습니다. 하지만 그 기발한 방법으로 보아 좋은 직관력을 가졌다고 생각되며, 동적계획법이든 뭐든 적당한 훈련 과정을 거친 사람이고 문제해결을 위한 코딩의 감각이 어느 정도 갖추어져 있는 사람임은 분명해 보입니다.


이전 글: 

2015/12/07 - [Technical/Development] - [프로그래밍] Fizzbuzz 문제에 대하여(1)



- Barracuda -


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

Barracuda

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


Fizzbuzz(피즈버즈) 문제. 프로그래머라면 한 번 쯤 풀어 보거나 들어본 경험이 있을지도 모르겠다. 만약 프로그래머로서의 직업을 가지려고 하거나, 단순한 취미로라도 "나 프로그램 좀 짠다" 라는 말을 할 수 있으려면 꼭 접해 보았어야할 문제다.


만약 Fizzbuzz 문제를 처음 듣거나, 예전에 들었는데 가물가물한다...하는 분이라면 이참에, 다시 한 번 스스로를 돌아보는 계기를 마련해 보자. 이건 글을 쓰는 본인에게도 해당하는 말이 될게다. Solid programming이나 Grok coding는 수 많은 고민과 노력에 의해 충분히 만들어 질 수 있다고 나는 믿는다. 중요한 건 엔지니어로서의 동기, 자부심 또는 열정 아니겠는가?



Fizzbuzz 문제가 뭐임?


"Fizzbuzz questions"는 영국의 아이들이 학교에서 하게되는 일종의 놀이다(를 통한 동기 또는 흥미 유발을 위한). 해외의 학교에서는 놀이식으로 수업을 진행하는 경우가 많은 편인데, 우리 것과 궂이 비교하자면 일종의 369 게임과 비슷할까?


어쨌든 Fizzbuzz 문제를 프로그래머 채용 인터뷰시에 내 보았더니 대다수의 우수한 프로그래머의 경우 2분 이내로 답을 내어야 함에도 대부분의 컴퓨터과학 전공학과 졸업생들이 문제 자체를 제대로 풀지 못했고, 심지어 좀 한다는 시니어 프로그래머들도 솔루션을 내는데 10~15분이 걸렸다고 한다.


[조금 오래 된(2007~) 관련 페이지들]

프로그래머들이 왜 프로그램을 못짜?(CodingHorror)

Fizzbuzz 에 대해 너무 깊게 생각하지 말라(RaganWald)

Fizzbuzz로 코딩 잘하는 개발자 찾기(Imran)

Fizzbuzz still works(GlobalNerdy)



Fizzbuzz 문제를 활용하는 이유


"소프트웨어를 개발하다", "프로그램을 짜다" 는 말을 보통 "코딩한다" 라고 표현한다. 코딩을 잘한다는 건 무엇일까? "최소한의 시간에 주어진 문제를 정확히 해결하는 프로그램을 잘 만들 수 있는가" 라는 질문에서 어느 정도 답을 찾을 수 있겠지만 문제가 그리 간단하지만은 않다. 


결과가 빨리 나왔다고 해서 Sold 한 프로그램이라고 단정지을 수는 없으며, 코딩 이전의 설계와 코딩 이후의 추후 확장이 필요한 소프트웨어의 특성상, 전체적인 구조에 대한 설계에 충분한 시간과 공을 들여야 할 수도 있다. 또 결과에 오류가 없을 뿐 아니라, 요건 변경에 대비하여 확장성이 충분해야 하는 솔루션이 필요할 수도 있기 때문이다. 그렇다면 Fizzbuzz 문제 같은 간단한 코딩테스트를 전화 또는 대면인터뷰시에 사용하는 이유는 무엇일까?


위의 관련페이지들의 내용을 찬찬히 훑어 보면 답은 명확하다. 바로, 코딩을 잘하는 사람을 뽑기 보다는 코딩을 못하는 사람 즉, 최근 몇 개월간 코딩을 직접 경험해 보지 못한 사람, 코딩을 잘 하려는 노력이나 고민을 게을리 하는 사람을 가려내어, 채용 회사와 면접자의 불필요한 시간 손실을 일단 줄여보겠다는 단순한 의도라고 짐작할 수 있다.



Fizzbuzz 문제와 솔루션들


[문제(영어 원문)]

Write a program that prints the numbers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz".

우리 말로 풀어서 쓰면, 1부터 100사이의 숫자를 프린트하는 프로그램을 작성하는데 3의 배수이면 "Fizz"를, 5의 배수이면 "Buzz"를, 둘 모두의 배수 즉 15의 배수이면 "FizzBuzz" 를 프린트하도록 하라.


문제는 한마디로 간단하고 명확하다. 인터뷰시에 해당되는 중요한 얘기인데, 만약 문제에 구체적인 제약사항이 필요하다면 적극적으로 면접관에게 질문하고 상의하라. 문제가 요구하는 답은 다음과 같은 모양일 것이다.


1

2

Fizz

4

Buzz

.

.

또는

1 2 Fizz 4 Buzz Fizz 7 ...


본인이 스스로 능력있는 코더라고 자부한다면 2분 내외로 실제로 동작하는 프로그램 코드가 완성되어야 한다. 실제로 면접시 제시되는 방법은 다양하다. 전화인터뷰시에 말로 설명해야 될 수도 있고, 화이트보드에 손으로 코딩하거나(손코딩 & 눈디버깅 & 뇌컴파일) 또는 실제로 코딩할 수 있는 노트북이나 작업 PC가 주어질 수도 있다. 그때 그때 상황에 따라 잘 대처해야 하겠다.


만약 한 번도 이런 퀴즈나 문제를 접해본 적이 없는 응시자하면 적잖이 당황하게 될 것이다. 학교나 학원의 교수나 선생님들은 이런 문제의 해결법을 따로 가르쳐 주지 않기 때문이고, 그 해법이 너무나 다양할 수 있기 때문일 것이다. 또 결과는 언뜻 단순해 보이지만 해결 방법이 그리 썩 단순하지는 않으며, 뭔가 트릭을 쓰면 '쌈빡'한 방법으로 해결될 듯도 하기 때문에 더 머리 속이 복잡해 질지도 모른다.


[참고]

오메가: 수학귀신들의 잡학사전 - FizzBuzz 테스트

c2.com - Fizz Buzz Test


위의 c2.com 과 같은 사이트의 페이지들에는 다양한 개발 언어로 된 샘플 소스들이 많이 올라와 있다. 또 구글링을 통해 검색해 보면 다양한 Fizzbuzz 솔루션들을 접해 볼 수 있다. 참고로 위의 관련페이지들에 링크로 소개된 Imran의 사이트의 페이지에는 최근까지도 새로운 댓글들이 올라오고 있는 실정이다.


printf("1\n");

printf("2\n");

printf("Fizz\n"); ...


이런 답들도 있기는 하다 ^^;; 심지어...


for(...) {

    if(if i == 1) printf("1\n");

    else if ...


이런 답도 있다 ㅠㅠ.


본 글에서는 가장 직관적이고 보기 쉬운(평범한 필자의 수준에서 직접 작성한 것이기에) 대표적인 샘플 몇 개와 장난기 섞인 것들 몇 개를 소개한다. 주로 C/C++과 javascript, Python 으로 작성된 것들을 소개해 둔다.


글 맨 마지막에는, 다음글에 연속으로 게재될 엽기적인 답안 하나를 제시해 놓으려 하니 잘 읽어두시고 한 번 실행해 보기를 권한다.


Example 1, 2, 3 은 C++ 이지만 cout 만 제외하면 C 에서도 그대로 적용 가능하며, Example 4, 5, 6은 C의 printf 함수와 삼항조건연산자등을 사용하는 트릭들이 들어 있다.



C++ Example 1 : 2분만에 풀기에 딱 좋은 단순한 구성

#include <iostream>

using std::cout;

void fizzbuzz1(int _n)
{
    for(int i = 1; i <= _n; i++) {
        if(i % 15 == 0)
            cout << "fizzbuzz";
        else if(i % 3 == 0)
            cout << "fizz";
        else if(i % 5 == 0)
            cout << "buzz";
        else
            cout << i;
        cout << endl;
    }
}

int main()
{
    fizzbuzz1(100);
    return 0;
}


(아래 부터는 main()을 제외한 처리 함수 부분만 표기한다)


C++ Example 2 : 첫 번째 솔루션은 뭔가 비교조건이 중복되게 느껴진다. 그렇다면...

void fizzbuzz2(int _n)
{
    int check_more = false;

    for(int i = 1; i <= _n; i++) {
        if(i % 3 == 0) {
            cout << "fizz";
            check_more = true;
        }
        if(i % 5 == 0) {
            cout << "buzz";
            check_more = true;
        }
        if(!check_more)
            cout << i;
        else
            check_more = false;
        cout << endl;
    }
}


C++ Example 3 : 역시 첫번째 솔루션에 대한 약간의 변형

void fizzbuzz3(int _n)
{
    for(int i = 1; i <= _n; i++) {
        if(i % 3 == 0)
            cout << "fizz";
        if(i % 5 == 0)
            cout << "buzz";
        else if(i % 3 != 0)
            cout << i;
        cout << endl;
    }
}


C++ Example 4, 5, 6 : 평범하게 풀기 싫다면 약간의 C 언어만의 트릭을 써 보자

void fizzbuzz4(int _n)
{
    for(int i = 1 ; i <= _n; i++) {
        if(i%3 && i%5) printf("%d", i);
        printf("%s%s\n", i%3 ? "" : "fizz", i%5 ? "" : "buzz");
    }
}
void fizzbuzz5(int _n)
{
    // printf 함수는 출력한 문자의 갯수를 return 한다
    // 연산자 우선순위에 주의한다
    int i = 0;

    while(i++<_n)
        (i%3 || !printf("Fizz")) * (i%5 || !printf("Buzz")) && printf("%d",i), printf("\n");
}
void fizzbuzz6(int _n)
{
    char i = 0, n[3];
    while(i++ < 100)
        printf("%s%s%s\n", i%3 ? "":"Fizz", i%5 ? "":"Buzz", (i%3&&i%5&&sprintf(n, "%d", i)) ? n : "");
}


Javascript Example 1 : 참 단순 명료한 javascript !

for (var i=1; i<=100; i++) {   
    console.log( ((i%3 ? '':'Fizz') + (i%5 ? '':'Buzz') || i) )
}


Python Example 1 : 위의 일반적인 경우보다는 좀 더 Pythonic 하게 작성해 보자

FizzBuzz 와 같은 2가지 경우가 아니라 n개의 확장된 case에 대해서도 쉽게 처리가 가능한 방식을 구현할 수 있다.

# -*- coding:utf-8 -*-

# 각각의 나누는 수는 고유의 이름을 가진다. 이를 divisor_pairs(divisor, name) 이라는 튜플로 설정한다.
# 1~n 까지의 각각의 수(i)에 따라,
#   ... 나누는 수(divisor) 의 name이 있으면 프린트하고
#   ... 없으면 숫자 자체를 프린트한다

divisor_pairs = [
    (3, "3으로나누어짐"),
    (5, "5로나누어짐"),
    (7, "7로나누어짐")
]

for i in range(1, 501):
    # i에 대해 나누어 떨어질 경우, name 으로 스트링을 만든다
    name_string = "".join(name for (divisor, name) in divisor_pairs if i % divisor == 0)
    # name_string 을 프린트한다. 만약 빈문자열이면 i 를 프린트한다
    print(name_string or i)



다음 2회에서 분석해 볼 엽기적 솔루션

[솔루션이 소개된 원문 페이지 보기FizzBuzz and bored programmers ...아우~ 이걸 채용해야 하나?

void fizzbuzz7(int _n)
{
    static const char *t[] = {"%d\n", "Fizz\n", "Buzz\n", "FizzBuzz\n"};

    for(int i = 1 ; i <= _n; i++) {
        printf(t[3 & 19142723 >> 2 * i % 30], i);
    }
}


다음 글: 2015/12/07 - [Technical/Development] - [프로그래밍] Fizzbuzz 문제에 대하여(2)



- Barracuda -

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

Barracuda

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


자주 만나는 헷갈리는 우리말 표현들 모음 II

(지난 관련 글-2012/12/13-에 이어서 추가할 만한 것들을 별도 포스팅으로 정리)




[~ㅓ, ~ㅔ, ~ㅐ, ~ㅖ]


1. ~건대, ~컨대

동사 또는 형용사 '~하다'와 '~건대' 가 합해지는 과정에서 'ㅏ' 가 빠지고 'ㅎ'이 남는 경우에 'ㄱ'과 결합하여 거센소리로 '~컨대' 가 됩니다. 따라서 "단언컨데", "단연컨데" 아니고 "단언컨대" 가 맞습니다. 당연히 "요컨데" 아니고 "요컨대" 가 맞습니다.


2. 요새

"요즈음" 이라는 뜻으로 쓰일 때는 "요세" 아니고 "요사이" 즉 "요새"가 맞습니다.


3. 재작년

"2 년 전" 이라는 의미로 쓰일 때에는 "제작년" 아니고 "재작년" 이 맞습니다. 1 년 전은 "작년".


4. 도대체

"도데채", "도데체" 아니고 "도대체" 가 맞습니다.



받침


1. "오랜만이다"

"오랫만" 아니고 오랜만


2. "앳되다"

"애띤 모습" 아니고 "앳된 모습"


3. "무난하다"

"문안하다" 아니고 "무난하다", 어렵지 않다(한자어)는 뜻.


4. '빈털털이'

소리 나는대로 '빈털터리' 라고 씁니다.


5. "건드리다"

"건들이다" 아니고 "건드리다". "건들거리다" 와 헷갈리면 안됩니다.


6. "널찍하다"

"넓직하다", "넓찍하다" 아닙니다. "너르다", "너그럽고 크다" 와 연관지어 둡니다.


7. '뒤치다꺼리'

'뒤치닥거리' 아닙니다. 비슷하게 헷갈리는 말로 '푸닥거리', '일거리', '마수걸이' 가 있습니다. '푸닥거리' 는 '푸다' 의 어원이 명확하지 않기 때문에 '푸다꺼리' 로 소리나는 대로 써야 한다는 설도 있습니다만, 현재 표준어 사전에는 '푸닥거리' 만 인정하고 있습니다.


8. '굳이'

"그걸 구지 말해야 하나"에서 '굳이' 로 씁니다. 이건 소리 나는대로 아닙니다. 


9. '구시렁'

"궁시렁거리다" 아니고 "구시렁거리다"


10. '움큼'

"한 움큼 거머쥐다". '웅큼' 아닙니다.


11. "닦달하다"

"닥달하다" 아닙니다. "닦다" 와 연관지어서 기업합니다. "남을 단단히 윽박질러서 혼을 내다" 또는 "물건을 손질하고 매만지거나 다듬다"의 뜻입니다.


12. "더욱이", "일찍이"

부사에 '~이' 가 붙어서 역시 부사가 되는 경우에 어근이나 부사의 원형을 적는 맞춤법의 원칙이 있습니다. 따라서 "더우기" 아니고 "더욱이", "일찌기" 아니고 "일찍이". 마찬가지로 "곰곰이, 생긋이, 오뚝이, 히죽이" 를 연관 짓습니다.


13. '구레나룻'

'구렛나룻', '구렌나루'  아니고 '구레나룻'



- Barracuda -


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

Barracuda

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



청와대가 보유한 조잡하고 치졸한 인터넷 도메인들


지난 2011년에 한겨레신문에 실린 웃픈 기사가 생각났다. 청와대가 "쥐박이.com , 쥐박이.kr, 쥐박이.net, 쥐박이.org. 명박이.kr..." 같은 한글키워드 도메인들을 2010년에 구매하여 보유하고 있다는 기사였다(기사 보기). 그렇다면 박근혜정부는 어떨까 싶어 몇 가지 키워드와 도메인들을 검색해 보았다. 다음을 보자.



우선 대표적인 한글키워드, '닭그네' 로 후이즈를 검색해 보았다. 예상했던 대로 닭그네.kr, 닭그네.com, 닭그네.net 3개의 도메인은 대통령비서실(postmaster@president.go.kr) 소유로 되어 있다. 나머지 "닭그네.한국" 한글도메인은 '미스터 jeon' 이라는 개인이 등록, 보유하고 있는 것으로 보인다.



도대체 뭐하러 이런 도메인을 세금을 들여서 보유하려 한 것일까? 내가 낸 세금으로 무언가를 차단하고 가리며 통제하려는 치졸한 수작을 보니, 참으로 답 없게 어리버리한 사람들이라는 생각이 든다. 조만간 소유자 정보가 비공개로 전환될지도 모르겠다.



대통령비서실이 보유한 국제인터넷 도메인들을 보니


다른 키워드 몇 개로 더 검색해 보려다 이게 뭐냐 싶어서 그만 두려던 차에, 리버스후이즈로 검색을 시도해 보기로 했다. 즉, 대통령비서실 명의로 등록된 국제 도메인을 찾아 보는 것이다. 방법은 간단하다. domainbigdata.com 에서 해당 이메일 소유자가 보유한 도메인 목록을 볼 수 있기 때문이다(검색해 보기).


등록 정보는 Office Of The President, Korea, Republic Of... 로 나오고, 결과를 보니 등록자(Registrant)가 "office of the president"로 되어 있는 인터넷 도메인이 총 89개로 보인다(목록 보기). 소유자명으로 검색을 했기 때문에 위에서 검색된 결과보다 더 많고, 이들 모두가 청와대 소유로 보이지는 않는다(대다수 나머지 잡다한 도메인들은 Private 처리가 되어 실 소유주를 확인할 수가 없다). 하지만 뭔가 의심스럽기는 하다.



예상했던 대로다. antibakgeunhye.com, antigeunhye-bak.com 등 "안티 박근혜" 의미로 조합할 수 있는 10개 가량의 국제 도메인 주소들이 보인다. 거참, 기왕에 보유할 거면 com, net 외에도 us, io, tv 같은 인기 있는 TLD들도 다 사버리지 그랬니?


쥐박이.com 이나 닭그네.com 이나 피같은 세금 엉뚱한 데 쓰는 작태는 여전하구나.



- Barracuda -


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

Barracuda

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