

drone-server
及 drone-agent
,拆成兩個好處是,通常 Server 端只會有一台,但是隨著專案越來越多,團隊越來越龐大,Agent 肯定不只有一台機器,所以把 Agent 拆出來可以讓維運人員架設新機器時更方便。
package main import ( "fmt" ) func main() { fmt.Println("Hello World!") }這是一個最簡單的程式碼,接著透過
go build
的方式編譯出執行檔,這時候我們看看檔案大小:
$ du -sh hello* 1.8M hello 4.0K hello.go一個 4K 大小的 Hello World,竟然要 1.8MB (使用 Go 1.9 版本),雖然說現在硬碟很便宜,網路傳輸也很快,但是對於大量 Deploy 到多台機器時,還是需要考量檔案大小,當然是越小越好,這時候我們可以使用 Go 語言的
shared
方式編譯出共同的 .a 檔案,將此檔案丟到全部機器,這樣再去編譯主執行擋,可以發現 Size 變成很小,應該不會超過 20K 吧。但是此種方式比較少人使用,大部分還直接將主程式編譯出來,這時來介紹另外一個工具用來減少 Binary 大小,叫做 UPX
UPX – the Ultimate Packer for eXecutables當然 upx 不只是可以壓縮 Go 的編譯檔案,其他編譯檔案也可以壓縮喔,底下是透過 upx 使用兩種不同的編譯方式來減少執行檔大小
$ du -sh hello* 1.8M hello # upx -o hello_1 hello 636K hello_1 # upx --brute -o hello_2 hello 492K hello_2 4.0K hello.go用了兩種方式來壓縮大小,越小的檔案,處理的時間越久,壓縮比例至少超過 70%,效果相當不錯,用 Hello World 沒啥感覺,實際拿個 Produciton 的案子來試試看,就拿 Gorush 來壓縮看看
$ du -sh * 15M gorush # upx --brute -o gorush_1 gorush 3.4M gorush_1 # upx -o gorush_2 gorush 4.7M gorush_2壓縮比例也差不多 70 %,但是使用
--brute
處理時間蠻久的,如果要加速部署,用一般模式即可。別因為些微差異,就花費多餘時間在壓縮上。
package.json
或 Dockerfile
來部署 app。原先剛出來時候,只有支援 node.js 部署,後來才增加 Docker。透過 Docker 就可以來部署各種不同語言的專案。
/usr/local/bin
或新增到 $PATH
變數底下。
server.go
package main import ( "flag" "time" "github.com/gin-gonic/gin" ) func rootHandler(context *gin.Context) { currentTime := time.Now() currentTime.Format("20060102150405") context.JSON(200, gin.H{ "current_time": currentTime, "text": "Hello World", }) } // GetMainEngine is default router engine using gin framework. func GetMainEngine() *gin.Engine { r := gin.New() r.GET("/", rootHandler) return r } // RunHTTPServer List 8000 default port. func RunHTTPServer() error { port := flag.String("port", "8000", "The port for the mock server to listen to") // Parse all flag flag.Parse() err := GetMainEngine().Run(":" + *port) return err } func main() { RunHTTPServer() }存檔後,請直接透過
go run
進行測試。
FROM golang:1.9-alpine3.6 MAINTAINER Bo-Yi Wu <appleboy.tw@gmail.com> ADD . /go/src/github.com/appleboy/go-hello RUN go install github.com/appleboy/go-hello EXPOSE 8000 CMD ["/go/bin/go-hello"]請修改
github.com/appleboy/go-hello
路徑,接著透過底下指令來編譯 Docker,注意 EXPOSE 8000
代表需要將 docker 內的 8000 對外。
$ docker build -t appleboy/go-hello -f Dockerfile .編譯成功後,使用 docker run 執行
$ docker run -p 8080:8000 appleboy/go-hello [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] GET / --> main.rootHandler (1 handlers) [GIN-debug] Listening and serving HTTP on :8000你可以發現成功執行了 app。
now
,就可以發現系統會自動上傳該專案目錄底下所有檔案。有些檔案是不需要上傳的話,請使用 .dockerignore
來自動忽略,node.js 則是透過 .npmignore
。如果沒有發現 .dockerignore
及 .npmignore
則是檢查 .gitignore
檔案。
Dockerfile
,假設今天把檔名換成 Dockerfile.now
這樣是無法讀取的,command 也沒有參數讓開發者修正。
.dockerignore
寫入
* !bin/ !config/可以發現加上
--debug
模式後,config 內的檔案沒有上傳到 now.sh,造成編譯失敗。
/var/lib/drone
路徑底下,但是容器內不支援寫入,所以必須要要額外掛上空間讓 Drone 可以寫入資料。此篇會以 GitHub 認證 + SQLite 來教學。
$ aws ec2 create-volume \ --availability-zone=ap-southeast-1a \ --size=1 --volume-type=gp2注意
availability-zone
區域要跟 K8S 同樣,大小先設定 1G。完成後會看到底下訊息:
{ "AvailabilityZone": "ap-southeast-1a", "Encrypted": false, "VolumeType": "gp2", "VolumeId": "vol-04741f74eb0f6b891", "State": "creating", "Iops": 100, "SnapshotId": "", "CreateTime": "2017-09-23T15:42:24.319Z", "Size": 1 }之後會用到
VolumeId
。假如沒有做此步驟,您會發現 Drone 伺服器是無法啟動成功。最後是請到 GitHub 帳號內建立一個全新 OAuth App,並取得 CLient
跟 Secret
代碼。
drone-server-deployment.yaml
,找到 volumeID
取代成上述建立好的結果。
awsElasticBlockStore: fsType: ext4 # NOTE: This needs to be pointed at a volume in the same AZ. # You need not format it beforehand, but it must already exist. # CHANGEME: Substitute your own EBS volume ID here. - volumeID: vol-xxxxxxxxxxxxxxxxx + volumeID: vol-01f13b969e9dabff7再來設定 server 跟 agent 溝通用的 Secret,打開
drone-secret.yaml
data: - server.secret: ZHJvbmUtdGVzdC1kZW1v + server.secret: ZHJvbmUtdGVzdC1zZWNyZXQ=透過 base64 指令換掉上面的代碼。假設密碼設定
drone-test-secret
,請執行底下指令
$ echo -n "drone-test-secret" | base64 ZHJvbmUtdGVzdC1zZWNyZXQ=在 GitHub 上面建立新的 Application,並且拿到 Client ID 跟 Secret Key,修改
drone-configmap.yaml
檔案
server.host: drone.example.com server.remote.github.client: xxxxx server.remote.github.secret: xxxxx接著陸續執行底下指令,新增 Drone NameSpace 並且將 server 及 agent 服務啟動
$ kubectl create -f drone-namespace.yaml $ kubectl create -f drone-secret.yaml $ kubectl create -f drone-configmap.yaml $ kubectl create -f drone-server-deployment.yaml $ kubectl create -f drone-server-service.yaml $ kubectl create -f drone-agent-deployment.yaml完成後,k8s 會自動建立 ELB,透過 kubectl 可以看到 ELB 名稱
$ kubectl --namespace=drone get service -o wide執行後看到底下結果:
NAME CLUSTER-IP EXTERNAL-IP drone-service 100.68.89.117 xxxxxxxxx.ap-southeast-1.elb.amazonaws.com拿到 ELB 網域後,可以直接更新 GitHub 的 application 資料
drone-server-deployment.yaml
內的 volumeID
。2. 申請 [GitHub OAuth Application][42],完成後請修改 drone-configmap.yaml
內的 GitHub 設定。最後執行底下指令
$ ./install-drone.sh
$ kubectl scale deploy/drone-agent \ --replicas=2 --namespace=drone其中
replicas
可以改成你要的數字。
$ kubectl delete -f drone-namespace.yaml
kubectl apply
)?原因是本篇會圍繞在 honestbee 撰寫的 drone 外掛: drone-kubernetes,此外掛是透過 Shell Script 方式搭配 kubectl 指令來完成升級 App 版本,可以看到程式原始碼並無用到 kubectl apply
方式來升級,也並非用 Go 語言搭配 k8s API 所撰寫,所以無法使用 YAML 方式來進行 Deployment 的升級。本篇講解的範例都可以在 drone-nodejs-example 內找到。底下指令就是外掛用來搭配 Drone 參數所使用。
$ kubectl set image \ deployment/nginx-deployment \ nginx=nginx:1.9.1
apiVersion: v1 kind: Namespace metadata: name: demo --- apiVersion: v1 kind: ServiceAccount metadata: name: drone-deploy namespace: demo --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: Role metadata: name: drone-deploy namespace: demo rules: - apiGroups: ["extensions"] resources: ["deployments"] verbs: ["get","list","patch","update"] --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: RoleBinding metadata: name: drone-deploy namespace: demo subjects: - kind: ServiceAccount name: drone-deploy namespace: demo roleRef: kind: Role name: drone-deploy apiGroup: rbac.authorization.k8s.io先建立
demo
namespace,再建立 drone-deploy 帳號,完成後就可以拿到此帳號 Token 跟 CA,可以透過底下指令來確認是否有 drone-deploy 帳號
$ kubectl -n demo get serviceAccounts NAME SECRETS AGE default 1 1h drone-deploy 1 1h接著顯示
drone-deploy
帳戶的詳細資訊
$ kubectl -n demo get serviceAccounts/drone-deploy -o yaml apiVersion: v1 kind: ServiceAccount metadata: creationTimestamp: 2017-10-10T13:09:55Z name: drone-deploy namespace: demo resourceVersion: "917006" selfLink: /api/v1/namespaces/demo/serviceaccounts/drone-deploy uid: 4f445728-adbc-11e7-b130-06d06b7f944c secrets: - name: drone-deploy-token-2xzqw可以看到
drone-deploy-token-2xzqw
此 secret name,接著從這名稱取得 ca.cert
跟 token
:
$ kubectl -n demo get \ secret/drone-deploy-token-2xzqw \ -o yaml | egrep 'ca.crt:|token:'由於 token 是透過
base64
encode 輸出的。那也是要用 base64 decode 解出
# linux: $ echo token | base64 -d && echo'' # macOS: $ echo token | base64 -D && echo''
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: k8s-node-demo namespace: demo spec: replicas: 3 template: metadata: labels: app: k8s-node-demo spec: containers: - image: appleboy/k8s-node-demo name: k8s-node-demo imagePullPolicy: Always ports: - containerPort: 8080 livenessProbe: httpGet: path: / port: 8080 initialDelaySeconds: 3 periodSeconds: 3 --- apiVersion: v1 kind: Service metadata: name: k8s-node-demo namespace: demo labels: app: k8s-node-demo spec: selector: app: k8s-node-demo # if your cluster supports it, uncomment the following to automatically create # an external load-balanced IP for the frontend service. type: LoadBalancer ports: - protocol: TCP port: 80 targetPort: 8080先定義好
replicas
數量,及建立好 AWS Load Balancer。完成後,基本上你可以看到第一版本成功上線
$ kubectl get service NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE k8s-node-demo 100.67.24.253 a4fba848aadbc... 80:30664/TCP 11h
publish: image: plugins/docker repo: appleboy/k8s-node-demo dockerfile: Dockerfile secrets: - source: demo_username target: docker_username - source: demo_password target: docker_password tags: [ '${DRONE_TAG}' ] when: event: tag請先到 Drone 專案後台的 Secrets 設定
demo_username
及 demo_password
內容。並採用 GitHub 流程透過 Git Tag 方式進行。接著設定 K8S Deploy:
deploy: image: quay.io/honestbee/drone-kubernetes namespace: demo deployment: k8s-node-demo repo: appleboy/k8s-node-demo container: k8s-node-demo tag: ${DRONE_TAG} secrets: - source: k8s_server target: plugin_kubernetes_server - source: k8s_cert target: plugin_kubernetes_cert - source: k8s_token target: plugin_kubernetes_token when: event: tag請注意
k8s_cert
跟 k8s_token
,你需要拿上面的 ca.cert
跟 token
內容新增到 Drone Secrets 設定頁面。
$ app -c config.yaml這樣測試同事拿到執行檔時,就可以透過
-c
參數來讀取個人設定檔。有個問題,假設設定檔需要動態修改,每次測完就改動一次有點麻煩,所以 App 必須要支援環境變數,像是如下:
$ APP_PORT=8088 app -c config.yaml假如沒有帶入
-c
參數,App 要能讀取系統預設環境設定檔案,像是 ($HOME/.app/config.yaml
)。下面來教大家如何透過 Viper 做到上述環境。
var defaultConf = []byte(` app: port: 3000 `)接著設定 Viper 讀取
Yaml
檔案型態。
viper.SetConfigType("yaml")
-c
參數
flag.StringVar(&configFile, "c", "", "Configuration file path.")接著就可以直接讀取 Yaml 檔案
if configFile != "" { content, err := ioutil.ReadFile(confPath) if err != nil { return conf, err } viper.ReadConfig(bytes.NewBuffer(content)) }可以看到透過
viper.ReadConfig
可以把 Yaml 內容丟進去,之後就可以透過 viper.GetInt("app.port")
來存取資料。
/etc/app/
(Linux 常用的 /etc/
目錄)$HOME/.app
(家目錄底下的 .app
目錄).
(執行當下目錄)config
開頭的設定檔案
viper.SetConfigName("config")上面設定好,就會直接找
config.yaml
檔案,如果設定 app
則是找 app.yaml
。接著指定設定檔所在目錄
viper.AddConfigPath("/etc/app/") viper.AddConfigPath("$HOME/.app") viper.AddConfigPath(".")最後透過
ReadInConfig
來自動搜尋並且讀取檔案。
if err := viper.ReadInConfig(); err == nil { fmt.Println("Using config file:", viper.ConfigFileUsed()) }
// read in environment variables that match viper.AutomaticEnv()接著設定環境變數 Prefix,避免跟其他專案衝突
// will be uppercased automatically viper.SetEnvPrefix("test")最後設定環境變數的分隔符號從
.
換成 _
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))以上面的例子來說,你可以透過
TEST_APP_PORT
來指定不同的 port
$ TEST_APP_PORT=3001 app -c config.yaml
-c
參數),那就不會執行 2, 3 步驟,步驟 1 省略的話,App 就會自動先找預設路徑,如果預設路徑找不到就會執行步驟 3。程式碼範例如下:
viper.SetConfigType("yaml") viper.AutomaticEnv() // read in environment variables that match viper.SetEnvPrefix("gorush") // will be uppercased automatically viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) if confPath != "" { content, err := ioutil.ReadFile(confPath) if err != nil { return conf, err } viper.ReadConfig(bytes.NewBuffer(content)) } else { // Search config in home directory with name ".gorush" (without extension). viper.AddConfigPath("/etc/gorush/") viper.AddConfigPath("$HOME/.gorush") viper.AddConfigPath(".") viper.SetConfigName("config") // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { fmt.Println("Using config file:", viper.ConfigFileUsed()) } else { // load default config viper.ReadConfig(bytes.NewBuffer(defaultConf)) } }
8088
port,可以透過 Docker 或直接用 Command line 執行,對於 App 開發者,可以直接下載執行檔,在自己電腦端發送訊息給手機測試。藉由這次投稿順便發佈了新版本。底下來說明新版本多了哪些功能。
package main import ( "log" pb "github.com/appleboy/gorush/rpc/proto" "golang.org/x/net/context" "google.golang.org/grpc" ) const ( address = "localhost:9000" ) func main() { // Set up a connection to the server. conn, err := grpc.Dial(address, grpc.WithInsecure()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() c := pb.NewGorushClient(conn) r, err := c.Send(context.Background(), &pb.NotificationRequest{ Platform: 2, Tokens: []string{"1234567890"}, Message: "test message", }) if err != nil { log.Fatalf("could not greet: %v", err) } log.Printf("Success: %t\n", r.Success) log.Printf("Count: %d\n", r.Counts) }
GORUSH_
$ GORUSH_CORE_PORT=8089 gorush
env: - name: GORUSH_STAT_ENGINE valueFrom: configMapKeyRef: name: gorush-config key: stat.engine - name: GORUSH_STAT_REDIS_ADDR valueFrom: configMapKeyRef: name: gorush-config key: stat.redis.host
{ "notifications": [ { "tokens": ["token_a", "token_b"], "platform": 1, "message": "Hello World iOS!" } ] }可以加上
development
或 production
布林參數,底下是將訊息傳給 iOS 開發伺服器
{ "notifications": [ { "tokens": ["token_a", "token_b"], "platform": 1, "development": true, "message": "Hello World iOS!" } ] }
YAML
來設定,開發者只需要專注於寫程式就可以了。
livenessProbe
設定。底下是在 Dockerfile 內可以設定 HEALTHCHECK
來
達到檢查容器是否存活。詳細說明可以參考此連結。
HEALTHCHECK --interval=5m --timeout=3s \ CMD curl -f http://localhost/ || exit 1
*.proto
檔案,並且寫入
message HealthCheckRequest { string service = 1; } message HealthCheckResponse { enum ServingStatus { UNKNOWN = 0; SERVING = 1; NOT_SERVING = 2; } ServingStatus status = 1; } service Health { rpc Check(HealthCheckRequest) returns (HealthCheckResponse); }存檔後重新產生 Go 程式碼: 檔案存放在
rpc/proto
目錄
$ protoc -I rpc/proto rpc/proto/gorush.proto --go_out=plugins=grpc:rpc/proto或者在 Makefile 內驗證 proto 檔案是否變動才執行:
rpc/proto/gorush.pb.go: rpc/proto/gorush.proto protoc -I rpc/proto rpc/proto/gorush.proto \ --go_out=plugins=grpc:rpc/proto
health.go
package rpc import ( "context" ) // Health defines a health-check connection. type Health interface { // Check returns if server is healthy or not Check(c context.Context) (bool, error) }
Check
接口
type Server struct { mu sync.Mutex // statusMap stores the serving status of the services this Server monitors. statusMap map[string]proto.HealthCheckResponse_ServingStatus } // NewServer returns a new Server. func NewServer() *Server { return &Server{ statusMap: make(map[string]proto.HealthCheckResponse_ServingStatus), } }這邊可以看到,gRPC 的狀態可以從 proto 產生的 Go 檔案拿到,打開
*.pb.go
,可以找到如下
type HealthCheckResponse_ServingStatus int32 const ( HealthCheckResponse_UNKNOWN HealthCheckResponse_ServingStatus = 0 HealthCheckResponse_SERVING HealthCheckResponse_ServingStatus = 1 HealthCheckResponse_NOT_SERVING HealthCheckResponse_ServingStatus = 2 )接著來實現 Check 接口
// Check implements `service Health`. func (s *Server) Check(ctx context.Context, in *proto.HealthCheckRequest) (*proto.HealthCheckResponse, error) { s.mu.Lock() defer s.mu.Unlock() if in.Service == "" { // check the server overall health status. return &proto.HealthCheckResponse{ Status: proto.HealthCheckResponse_SERVING, }, nil } if status, ok := s.statusMap[in.Service]; ok { return &proto.HealthCheckResponse{ Status: status, }, nil } return nil, status.Error(codes.NotFound, "unknown service") }上面可以看到透過帶入
proto.HealthCheckRequest
得到 gRPC 的回覆,這邊通常都是帶空值,
gRPC 會自動回 1
,最後在啟動 gRPC 服務前把 Health Service 註冊上去
s := grpc.NewServer() srv := NewServer() proto.RegisterHealthServer(s, srv) // Register reflection service on gRPC server. reflection.Register(s)這樣大致上完成了 gRPC 伺服器端實作
client.go
裡面寫入
package rpc import ( "context" "github.com/appleboy/gorush/rpc/proto" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) // generate protobuffs // protoc --go_out=plugins=grpc,import_path=proto:. *.proto type healthClient struct { client proto.HealthClient conn *grpc.ClientConn } // NewGrpcHealthClient returns a new grpc Client. func NewGrpcHealthClient(conn *grpc.ClientConn) Health { client := new(healthClient) client.client = proto.NewHealthClient(conn) client.conn = conn return client } func (c *healthClient) Close() error { return c.conn.Close() } func (c *healthClient) Check(ctx context.Context) (bool, error) { var res *proto.HealthCheckResponse var err error req := new(proto.HealthCheckRequest) res, err = c.client.Check(ctx, req) if err == nil { if res.GetStatus() == proto.HealthCheckResponse_SERVING { return true, nil } return false, nil } switch grpc.Code(err) { case codes.Aborted, codes.DataLoss, codes.DeadlineExceeded, codes.Internal, codes.Unavailable: // non-fatal errors default: return false, err } return false, err }
check.go
package main import ( "context" "log" "time" "github.com/go-training/grpc-health-check/rpc" "google.golang.org/grpc" ) const ( address = "localhost:9000" ) func main() { // Set up a connection to the server. conn, err := grpc.Dial(address, grpc.WithInsecure()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() client := rpc.NewGrpcHealthClient(conn) for { ok, err := client.Check(context.Background()) if !ok || err != nil { log.Printf("can't connect grpc server: %v, code: %v\n", err, grpc.Code(err)) } else { log.Println("connect the grpc server successfully") } <-time.After(time.Second) } }
/healthz
來同時處理 gRPC 及 Http 服務的驗證。在 Kubernetes 內就可以透過設定 livenessProbe
來驗證 Container 是否存活。
livenessProbe: httpGet: path: /healthz port: 3000 initialDelaySeconds: 3 periodSeconds: 3
server { # don't forget to tell on which port this server listens listen 80; # listen on the www host server_name blog.wu-boy.com; location /.well-known/acme-challenge/ { alias /var/www/dehydrated/; } # and redirect to the non-www host (declared below) return 301 https://blog.wu-boy.com$request_uri; } server { listen 0.0.0.0:443 ssl http2; location /.well-known/acme-challenge/ { alias /var/www/dehydrated/; } ssl_certificate /etc/dehydrated/certs/blog.wu-boy.com/fullchain.pem; ssl_certificate_key /etc/dehydrated/certs/blog.wu-boy.com/privkey.pem; # The host name to respond to server_name blog.wu-boy.com; # Path for static files root /home/www/blog/www; index index.html index.htm index.php; access_log /home/www/blog/log/access.log; error_log /home/www/blog/log/error.log; # Specify a charset charset utf-8; # disable autoindex autoindex off; # Deliver 404 instead of 403 "Forbidden" error_page 403 /403.html; # Custom 404 page error_page 404 /404.html; # stop image Hotlinking # ref: http://nginx.org/en/docs/http/ngx_http_referer_module.html location ~ .(gif|png|jpe?g)$ { valid_referers none blocked server_names; if ($invalid_referer) { return 403; } } location = / { index index.html index.html index.php; } include h5bp/basic.conf; include h5bp/module/wordpress.conf;看到都頭昏眼花了。這還沒有附上
wordpress.conf
的設定呢。接著我們看一下 Caddy 的設定
blog.wu-boy.com { root /home/www/blog/www gzip fastcgi / unix:/var/run/php5-fpm.sock php rewrite { if {path} not_match ^\/wp-admin to {path} {path}/ /index.php?{query} } }有沒有差異很大,Caddy 強調的就是簡單,而且會自動更新網站憑證。
codeigniter.org.tw { root /home/www/ci/www gzip fastcgi / unix:/var/run/php5-fpm.sock php rewrite { if {path} not_match ^\/(forum|user_guide|userguide3) to {path} {path}/ /index.php?{query} } }
xxxx.wu-boy.com { proxy / localhost:8081 { websocket transparent } }
pipeline: test1: image: mhart/alpine-node:9.1.0 group: testing secrets: [ test46 ] commands: - echo "node.js" - echo $TEST46 test2: image: appleboy/golang-testing group: testing secrets: [ test46, readme ] commands: - echo "golang" - echo $TEST46 - echo $README從上面可以得知,透過
appleboy/golang-testing
或 mhart/alpine-node:9.1.0
都可以存取 test46
變數,這樣哪裡不安全?答案是,假設今天服務的是開源專案,這樣別人是不是可以發個 PR,內容新增一個步驟,將變數內容直接印出來即可。當然你可以把 Drone 的頁面關閉,只有管理者可以存取,但是這樣就失去開源專案的意義,因為貢獻者總該需要看到哪裡編譯錯誤,或者是測試失敗的地方。
$ drone secret -h NAME: drone secret - manage secrets USAGE: drone secret command [command options] [arguments...] COMMANDS: add adds a secret rm remove a secret update update a secret info display secret info ls list secrets OPTIONS: --help, -h show help先假設
ssh-password
變數需要綁定在 appleboy/drone-ssh
映像檔上面,該如何下指令:
$ drone secret add \ --name ssh-password \ --value 1234567890 \ --image appleboy/drone-ssh \ --repository go-training/drone-workshop上述例子可以用在存密碼欄位,如果是想存
檔案
類型呢?也就是把金鑰 public.pem
給存進變數。這邊可以透過 @檔案路徑
的方式來存取該檔案,並且直接寫入到 Drone 資料庫。注意只要是 @
開頭,後面就必須接實體檔案路徑。
$ drone secret add \ --name ssh-key \ --value @/etc/server.pem \ --image appleboy/drone-ssh \ --repository go-training/drone-workshop
node_modules
內不必要的檔案,那哪些才是不必要的檔案呢?
檔案列表
var DefaultFiles = []string{ "Makefile", "Gulpfile.js", "Gruntfile.js", ".DS_Store", ".tern-project", ".gitattributes", ".editorconfig", ".eslintrc", ".jshintrc", ".flowconfig", ".documentup.json", ".yarn-metadata.json", ".travis.yml", "LICENSE.txt", "LICENSE", "AUTHORS", "CONTRIBUTORS", ".yarn-integrity", }預設
刪除目錄
var DefaultDirectories = []string{ "__tests__", "test", "tests", "powered-test", "docs", "doc", ".idea", ".vscode", "website", "images", "assets", "example", "examples", "coverage", ".nyc_output", }預設刪除的
副檔名
var DefaultExtensions = []string{ ".md", ".ts", ".jst", ".coffee", ".tgz", ".swp", }作者也非常好心,開了
WithDir
, WithExtensions
… 等接口讓開發者可以動態調整名單。其實這專案不只可以用在 Node.js 專案,也可以用在 PHP 或者是 Go 專案上。
/usr/local/bin
底下即可。使用方式很簡單,到專案底下直接執行 node-prune
$ node-prune files total 16,674 files removed 4,452 size removed 18 MB duration 1.547s
master
其他分支都是從主分支在開出來,所以新人很容易理解,不管是解 Issue 還是開發新功能,都是以 master
分支為基底來建立新的分支,開發團隊也只需要懂到這邊就可以了。接下來 Deploy 到 Production 則是透過 Tag 方式來解決。由開發團隊主管來下 Tag,這樣可以避免團隊內部成員不小心合併分支造成 Deploy 到正式環境的錯誤狀況。另外大家會遇到上線後,如何緊急上 Patch 並且發佈下一個版本,底下是最簡單的操作步驟。
# 抓取遠端所有 tag $ git fetch -t origin # 從上一版本建立 branch (0.2.4 代表上一個版本) $ git checkout -b patch-1 origin/0.2.4 # 把修正個 commit 抓到 patch-1 branch $ git cherry-pick commit_id # 打上新的 Tag 觸發 Deploy 流程 $ git tag 0.2.5 $ git push origin 0.2.5 # 將 patch 也同步到 master 分支 $ git checkout master $ git cherry-pick commit_id $ git push origin master有沒有覺得跟 Git Flow 流程差異很多,大家只需要記住兩件事情,第一是專案內只會有
master
分支需要受到保護。第二是部署流程一律走 Tag 事件,這樣可以避免工程師不小心 Merge commit 造成提前部署,因為平常開發過程,不會有人隨便下 Git Tag,所以只要跟團隊同步好,Git Tag 都由團隊特定人士才可以執行即可。底下附上團隊內的流程:
master
上單一節點去下 tag 標記為 1.0.0
,這沒問題,這版本釋出之後,CI/CD 服務會自動依照 Tag 事件來自動化部署軟體到 GitHub Release 頁面。但是軟體哪有沒 bug 的,一但發現 Bug,這時候想發 Pull Request 回饋給開源專案,會發現只能針對 master 發 Pull Request,該專案團隊這時候就需要在下完 Tag 同時建立 release/v1.0
分支,方便其他人在發 PR 時,在 review 完成後合併到 master 內,接著團隊會告知這 PR 需要被放到 release/v1.0
內方便釋出下一個版本 v1.0.1
,所以我才會下這個結論,一個好的開源專案是需要兩個 Flow 同時使用。而在開源專案上的好處是,你不用擔心別人不會 Git 流程或指令。基本上不會用 Git 的開發者,也不會發 Pull Request 了。
.drone.yml
檔案方式讓開發者可以自行撰寫測試及部署流程。大家一定會認為要先架設好 Drone 伺服器,才能透過 Git Push 方式來達到自動化測試及部署專案。現在跟大家介紹,如果你的團隊尚未架設 Drone 服務,但是又想要使用 Drone 透過 Yaml 方式所帶來的好處,很簡單,你只需要透過 Drone CLI 工具就可以完成,不需要架設任何一台 Drone 服務,只要學會 Yaml 方式如何撰寫,就可以透過 drone exec
指令來完成。好處是寫完 .drone.yml 檔案,未來圖隊如果正式架設了 Drone 服務,就可以無痛升級,沒有的話,也可以透過 CLI 工具在公司專案內單獨使用,這比寫 docker-compose.yml 方式還要快很多。本篇會介紹使用 drone exec
的小技巧。
/usr/local/bin
底下,目前支援 Windows, Linnx 及 MacOS。如果開發環境有 Go 語言,可以直接透過底下指令安裝
$ go get -u github.com/drone/drone-cli/drone或是透過 tarbal 方式安裝
curl -L https://github.com/drone/drone-cli/releases/download/v0.8.0/drone_linux_amd64.tar.gz | tar zx sudo install -t /usr/local/bin drone
.drone.yml
檔案
pipeline: backend: image: golang commands: - echo "backend testing" frontend: image: golang commands: - echo "frontend testing"在命令列直接下
drone exec
畫面如下
[backend:L0:0s] + echo "backend testing" [backend:L1:0s] backend testing [frontend:L0:0s] + echo "frontend testing" [frontend:L1:0s] frontend testing可以發現今天就算沒有 drone server 團隊依然可以透過 drone exec 來完成測試。
pipeline: backend: image: golang secrets: [ test ] commands: - echo "backend testing" - echo $TEST frontend: image: golang commands: - echo "frontend testing"執行
drone exec
後會發現結果如下
$ drone exec [backend:L0:0s] + echo "backend testing" [backend:L1:0s] backend testing [backend:L2:0s] + echo $TEST [backend:L3:0s] [frontend:L0:0s] + echo "frontend testing" [frontend:L1:0s] frontend testing可以得知
$TEST
輸出是沒有任何資料,但是如果在 Drone server 上面跑是有資料的。那該如何在個人電腦也拿到此資料呢?其實很簡單,透過環境變數即可
$ TEST=appleboy drone exec [backend:L0:0s] + echo "backend testing" [backend:L1:0s] backend testing [backend:L2:0s] + echo $TEST [backend:L3:0s] appleboy [frontend:L0:0s] + echo "frontend testing" [frontend:L1:0s] frontend testing這樣我們就可以正確拿到 secret 資料了。
.drone.yml
檔案內,但是在本機端只想跑前後端測試,後面的像是 Notification,或者是 SCP 及 SSH 步驟都需要忽略,這樣可以單純只跑測試,這時候該透過什麼方式才可以避免呢?很簡單只要在 when
條件子句加上 local: false
即可。假設原本 Yaml 寫法如下:
pipeline: backend: image: golang commands: - echo "backend testing" frontend: image: golang commands: - echo "frontend testing" deploy: image: golang commands: - echo "deploy"這次我們想忽略掉
deploy
步驟,請改寫如下
pipeline: backend: image: golang commands: - echo "backend testing" frontend: image: golang commands: - echo "frontend testing" deploy: image: golang commands: - echo "deploy" when: local: false再執行
drone exec
,大家可以發現,最後一個步驟 deploy
就被忽略不執行了,這在本機端測試非常有用,也不會影響到 drone server 上的執行。大家可以參考此 Yaml 檔案範例,大量使用了 local: false
方式。
drone exec
方式來測試看看 drone 的優勢及好處,並且可以取代 docker-compose 無法做到的平行處理喔。
http
並非使用 https
,這在搭配 Kubernetes 會有個問題,因為假設使用 http
的話,Docker 預設是不吃 http 的,所以必須要在 k8s 每一個 Node 機器內補上下面設定
# open /etc/docker/daemon.json { "debug" : true, "insecure-registries" : [ "harbor.xxxx.com" ] }如果在個人電腦上面 (Mac) 則是需要到底下 Docker 設定頁面補上 register 資訊
~/.caddy/
目錄,接著透過 link 方式放到 /data
目錄 (/data
是 Harbor 預設放在 Host 的目錄)
ln -sf ~/.caddy/acme/acme-v01.api.letsencrypt.org/sites/your_domain.com/harbor.wu-boy.com.key /data/cert/server.key ln -sf ~/.caddy/acme/acme-v01.api.letsencrypt.org/sites/your_domain.com/harbor.wu-boy.com.cert /data/cert/server.cert接著打開
harbor.cfg
將 ui_url_protocol
設定為 https
ui_url_protocol = https重新啟動 harbor
$ ./prepare $ docker-compose down $ docker-compose up -d
https
然後 Caddy 也跑 https
才行
your_domain.com { log stdout proxy / https://your_domain.com:8089 { websocket transparent } }其中 8089 就是對應到 harbor 容器內的 443 port。這樣還不夠,你必須要在
/etc/hosts
底下補上
127.0.0.1 your_domain.com這樣才可以正確讓 Caddy + Harbor 正式跑起來,並且三個月自動更換憑證。