[教學] 用Drone, Kubernetes跟Helm,以及RBAC來建置你的CI/CD流程 — Part 2

CI/CD with Drone, Kubernetes and Helm — Part 2

Cloudsan Chen
Sep 8, 2018 · 13 min read

有陣子沒寫文章,翻譯點東西來練習練習。最近公司要導入Drone/K8s/Helm,加上本來公司大多數的解決方案都放在Google Cloud 上,於是開始動手在新的專案做測試。不過這幾個解決方案都算新的,也很難找到適合的說明一次全部到位。剛好看到 這篇 在介紹這個組合,不但救了我幾天的時間,還幫我暖了一下這幾個技術的概念,聯絡了一下作者,決定來翻譯這篇文章。不過應該很難避免加入自己的話啦,英文的部分除非比較常見的翻譯法,否則還是以原文為主。另外原作者列了許多參考連結,在此就沒有再針對那些連結在做翻譯了


介紹

這是系列的第二篇,第一篇請看此。在第一篇的時候我們看到怎麼開始一個k8s的叢集、怎麼部署Tiller、怎麼運用Helm跟Tiller互動,並且部署一個Drone的服務。我們也用cert-manager讓我們的Drone支援HTTPS

在這篇文章我們會看到怎麼用Go的專案來建立一個有效的工作流程(Pipeline),如何建置一個docker的鏡像(image)並且根據不同的事件透過我們的Drone把他推送到Google Cloud Registry

Go Project

Simple Project

TL;DR: 你可以看原作者提供的Sample程式 here. (註,其實你也可以隨意找一個docker化的專案)

我們會在一個測試用的go專案上實作。所以就先在你的版控系統上先開一個新的Repo,clone之後開一個main.go的檔案

package mainimport (
"net/http"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func main() {
r := gin.Default()
r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"alive": true})
})
if err := r.Run("127.0.0.1:8080"); err != nil {
logrus.WithError(err).Fatal("Couldn't listen")
}
}

這個專案會新建一個簡單的伺服器,監聽 127.0.0.1:8080,然後有個路由/health永遠回覆200

Tooling and Docker

就像前面文章所提到的,我們會用dep來處理我們的go 相依套件然後我們會有一個多步驟的Dockerfile

首先,我們就先安裝dep,然後在我們的repo裡初始化

$ go get -u github.com/golang/dep/cmd/dep
$ dep init
Locking in (a5b47d3) for transitive dep gopkg.in/yaml.v2
Locking in master (22d885f) for transitive dep github.com/gin-contrib/sse
Locking in (c88ee25) for transitive dep github.com/ugorji/go
Locking in v8.18.1 (5f57d22) for transitive dep gopkg.in/go-playground/validator.v8
Locking in master (1a580b3) for transitive dep golang.org/x/crypto
Locking in master (7c87d13) for transitive dep golang.org/x/sys
Locking in (57fdcb9) for transitive dep github.com/mattn/go-isatty
Using ^1.2.0 as constraint for direct dep github.com/gin-gonic/gin
Locking in v1.2 (d459835) for direct dep github.com/gin-gonic/gin
Locking in (5a0f697) for transitive dep github.com/golang/protobuf
Using ^1.0.5 as constraint for direct dep github.com/sirupsen/logrus
Locking in v1.0.5 (c155da1) for direct dep github.com/sirupsen/logrus

現在我們有兩個多的檔案:Gopkg.lockGopkg.toml。我們也有一個vendor目錄。好,我們的套件就初始化好了,接下來就建立我們的 Dockerfile:

# Build step
FROM golang:latest AS build
RUN mkdir -p $GOPATH/src/github.com/Depado/dummy
ADD . $GOPATH/src/github.com/Depado/dummy
WORKDIR $GOPATH/src/github.com/Depado/dummy
RUN go get -u github.com/golang/dep/cmd/dep
RUN dep ensure -vendor-only
RUN CGO_ENABLED=0 go build -o /dummy
# Final step
FROM alpine
RUN apk update
RUN apk upgrade
RUN apk add ca-certificates && update-ca-certificates
RUN apk add --update tzdata
RUN rm -rf /var/cache/apk/*
COPY --from=build /dummy /home/
ENV TZ=Europe/Paris
WORKDIR /home
ENTRYPOINT ./dummy
EXPOSE 8080

我們也在我們的.gitignore裡面把vendor/給去除,避免每次都會全部複製過去一次。(註:要不要在git裡面放vendor這件事有蠻多主張的,在go還沒提出更好的套件管理方法之前,就先觀望一下吧)

$ docker build .
...
Successfully built 9a763d6aa971
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 9a763d6aa971 1 minutes ago 23.5MB

很好,現在我們有一個小型的docker映像,裡面有我們的go程式,而且也準備可以把它部署到k8s上了

Drone 工作流

基本工作流

就像前一篇文章提到的,Drone的做法跟Travis一樣,你在Repo的根目錄建立一個.drone.yml

workspace:
base: /go
path: src/github.com/Depado/dummy
pipeline:
prerequisites:
image: "golang:latest"
commands:
- go version
- go get -u github.com/golang/dep/cmd/dep
- dep ensure -vendor-only

build:
image: "golang:latest"
commands:
- go build

這是一個最基本包含dep的工作流。第一步,先使用golang:latest這個映像檔、顯示go的版本、安裝dep、安裝套件。第二步就是建置我們的程式

Linter

(註,因為我個人用了另外的工具,所以linter這段我並沒有實作)這個部分我們會使用兩個linter,一個是gometalinter,這是一個蠻多人使用的linter,另外一個是 golangci-lint,這個比較新,但他的效能跟gometalinter相比快非常多,所以就自己選一個吧

gometalinter

linter:
image: "golang:latest"
commands:
- go get -u github.com/alecthomas/gometalinter
- gometalinter --install --force
- gometalinter --disable=gotype --vendor --deadline=5m ./...

golangci-lint

linter:
image: "golang:latest"
commands:
- go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
- golangci-lint run

意見

雖然原作者喜歡gometalinter,但他用了golangci-lint之後就沒有回去了。輸出的結果更好、速度更快,所以他個人強烈建議後者

推送到GCR

設定

這個部分我們會開始用Drone,以及Drone的密鑰。首先你要先確認你安裝了drone CLI, 而且你正確的設定好了。你可以執行下面的程式來確定你已經設定好了

$ drone info
User: you
Email: you@yourmail.com

Google Container Registry是一個私有的Docker容器雲,我們會拿它來放我們建立好的docker映像檔。因為他是私有的,所以我們必須要給予他必要的權限,方法就是透過一個服務帳戶的方式。這個帳戶並不會放在我們的k8s叢集裡,而是會被我們拿來授權給我們的Drone

所以首先先到 IAM Console 來建立一個新的服務帳戶。你可以自由的命名它,但必須給他Storage Admin這個權限,點選產生新JSON key,下載他、並且儲存他。

Latest

接下來我們會用一個Drone叫做 Google Container Registry plugin的plugin 。

所以接下來就把這個步驟新增到我們的.drone.yml吧

gcr:
image: plugins/gcr
repo: project-id/dummy
tags:
- latest
- "${DRONE_COMMIT_SHA}"
secrets: [google_credentials]
when:
event: push
branch: master

記得把上面的project-id換成你自己的專案ID,這個步驟表示的是:

如果一個commit被push到master的branch,並且推送到我們的GCR,我們會使用google_credentials密鑰來取用他。我們目前還沒有建立 google_credentials 這個密鑰。所以回到你的terminal,我們用drone的工具在新增這個密鑰到我們的drone(註:免費版的Drone強迫你每個Repo的密鑰要各自設定,超級惱人),並且我們限制他只會在這個plugins/gcr這個映像裡面被使用

$ drone secret add --image plugins/gcr --repository Depado/dummy \
--name google_credentials --value @your_key.json

接下來我們可以commit我們的檔案然後測試他了,如果沒意外的話,你會在你的GCR裡面看到一個建立好的映像檔,並且有一個latest tag

Release

現在我們把我們的映像檔上傳到GCR了,那如果我們想要處理tag時候的情形呢?我們也可以用底下的方式來做一樣的行為

tagged_gcr:
image: plugins/gcr
repo: project-id/dummy
tags:
- "${DRONE_TAG##v}"
- "${DRONE_COMMIT_SHA}"
- latest
secrets: [google_credentials]
when:
event: tag
branch: master

現在這個流程只會在我們有tag在我們的commit的時候執行,他也會在這個映像檔裡面加入${DRONE_TAG##v} tag。這個意思是Drone會把v這個詞移除掉,例如我們tag某個commit叫v1.0.1,在映像檔的tag會叫做1.0.1

總結

現在你的 .drone.yml看起來會像這樣

workspace:
base: /go
path: src/github.com/Depado/dummy
pipeline:
prerequisites:
image: "golang:latest"
commands:
- go version
- go get -u github.com/golang/dep/cmd/dep
- dep ensure -vendor-only
linter:
image: "golang:latest"
commands:
- go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
- golangci-lint run
build:
image: "golang:latest"
commands:
- go build
gcr:
image: plugins/gcr
repo: project-id/dummy
tags:
- latest
- "${DRONE_COMMIT_SHA}"
secrets: [google_credentials]
when:
event: push
branch: master
tagged_gcr:
image: plugins/gcr
repo: project-id/dummy
tags:
- "${DRONE_TAG##v}"
- "${DRONE_COMMIT_SHA}"
- latest
secrets: [google_credentials]
when:
event: tag
branch: master

現在我們的drone會檢查我們的code是不是通過所有的linter,確定我們的專案可以被編譯,建置docker映像檔,並且根據不同的事件將他上傳到GCR上。

下一篇文章你會看到怎麼替我們的應用程式建立一個Helm Chart。上一篇文章我們已經看到怎麼使用一個人加件好的Chart,接下來我們就可以看到我們怎麼自動化這段流程了!

系列文連結如下:

第一篇

第二篇

第三篇

Cloudsan Chen

Written by

Too lazy to be lazy. Lazy software engineer speaking

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade