[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