본문 바로가기

Technical/Development

[Kubernetes, Docker] Go 언어로 Tiny Web Server 만들고 docker hub에 공유하기


최근 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/>


* github repository: https://github.com/DragOnMe/tiny-goweb-for-k8s-test


- 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