天天看點

go程式基于阿裡雲CodePipeline的一次devops實踐

背景

最近朋友有個項目代碼托管用的碼雲,測試伺服器(阿裡雲ECS)隻有一台,三四個人開發,于是想基于阿裡雲的

CodePipeline

快速打造一套自動化cicd的流程,使用docker來進行多套環境部署。

CodePipeline 介紹

阿裡雲CodePipeline是相容Jenkins标準的、提供快速可靠的持續內建與持續傳遞服務。基于容器技術和阿裡雲基礎服務架構,提供穩定和安全的代碼/Docker編譯建構,測試,掃描和部署的工具服務,并提供Pipeline As Code的編碼級配置模式,滿足應用程式和基礎設施快速可靠的傳遞和更新。

上面是阿裡雲的官方介紹,相容jekins的标準,一次無意的點選出現了下圖的圖示,感覺就是jenkins的基礎上做了一次二次開發。

建立建構項目

建構項目基本配置

基本資訊配置

由于項目使用到了一些國外的lib,當然選擇國内的節點也是可以的,不過在寫Dockerfile的時候記得加上代理。

源碼管理配置

選擇合适的代碼倉庫,也可以是私倉,不過需要配上自己的賬号和ssh key相關資訊。

建構配置

我這裡使用的是阿裡雲的私有的鏡像倉庫基于Dockerfile來建構鏡像,注意配置憑證。

![在這裡插入圖檔描述]

項目目錄
gin_demo
├── app
│   └── app.go
├── conf
│   └── app.ini
├── Dockerfile
├── docs
│   └── sql
│       └── mjs.sql
├── go.mod
├── go.sum
├── main.go
├── middleware
│   ├── jwt
│   │   └── jwt.go
│   └── logging
│       └── logger.go
├── mjs.exe
├── models
│   ├── mongo
│   │   └── db.go
│   └── mysql
│       ├── db.go
│       ├── teacher.go
│       └── user.go
├── pkg
│   ├── app
│   │   ├── form.go
│   │   ├── request.go
│   │   └── response.go
│   ├── def
│   │   └── def.go
│   ├── e
│   │   ├── code.go
│   │   ├── def.go
│   │   └── msg.go
│   ├── file
│   │   └── file.go
│   ├── gredis
│   │   └── redis.go
│   ├── logging
│   │   ├── file.go
│   │   └── log.go
│   ├── setting
│   │   └── setting.go
│   ├── upload
│   │   └── image.go
│   └── util
│       ├── jwt.go
│       ├── md5.go
│       ├── pagination.go
│       └── util.go
├── README.en.md
├── README.md
├── routers
│   ├── api
│   │   └── v1
│   └── router.go
├── runtime
│   └── logs
│       ├── log20190528.log
│       └── log20190529.log
└── service
    ├── teacher_service
    │   └── teacher.go
    └── user_service
        └── user.go
           
Dockerfile
FROM golang:1.12.4 as build

#ENV GOPROXY https://goproxy.io 

WORKDIR /go/cache

ADD go.mod .
ADD go.sum .
RUN go mod download

WORKDIR /go/release

ADD . .

RUN GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w" -installsuffix cgo -o gin_demo main.go

FROM scratch as prod

COPY --from=build /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY --from=build /go/release/gin_demo /
COPY --from=build /go/release/conf ./conf
CMD ["/gin_demo"]           

注:如果選擇國内節點建構必須配上代理 ENV GOPROXY

https://goproxy.io

配置觸發器

這裡選擇一個建構的代碼分支,然後點選生成,将生成後的url可以在對應的代碼倉庫配上webhook之類的配置。選擇合适的觸發方式。

配置部署

阿裡雲建構步驟裡面部署到ECS上(不友善)和部署到k8s(收費)都不能滿足項目場景需求,不過上面支援shell腳本,于是自己想在朋友的那台阿裡雲ECS上跑個ci的web server,這裡建構完之後就去通知下這個接口,然後去執行一些docker指令的腳本,拉取建構完之後的鏡像并且重新啟動,啟動完之後郵件通知。于是寫了這樣一個很簡單的web server。

main

package main

import (
    "fmt"
    "log"
    "net/http"
    "os/exec"
    "path"
)

/*
* @Author:hanyajun
* @Date:2019/5/28 16:44
* @Name:ci_tools
* @Function: ci 工具
 */

//
func HandleCi(w http.ResponseWriter, req *http.Request) {
    user := path.Base(req.URL.Path)
    //針對不同開發人員的push拉取不同的鏡像和映射不同的端口
    var port string
    switch user {
    case "zhangsan":
        port = "8088"
    case "lisi":
        port = "8087"
    case "wangmazi":
        port = "8086"
    case "dev":
        port = "8085"
    case "master":
        port = "8084"

    }
    println(user)
    result := Run(user,port)
    client:=NewMailClient("smtp.qq.com",465,"******@qq.com","*********")//注意使用465 ssl發送,25端口阿裡雲ecs禁止了。
    s:=&SendObject{
        ToMails:[]string{"******@qq.com","******@qq.com","******@qq.com"},
        CcMails:[]string{"******@qq.com"},
        Object:"cicd流程結果通知",
        ContentType:"text/html",
        Content:user+" has push something to the branch: "+user+"\n"+"result: "+string(result),
    }
    err:=client.SendMail(s)
    if err!=nil{
        println("send mail fail",err)
    }
    _, _ = w.Write(result)
}
func main() {
    http.HandleFunc("/ci/", HandleCi)
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

func Run(user string,port string) []byte {

    shellPath := "/root/ci/ci.sh"
    command := exec.Command(shellPath, user,port) 
    err := command.Start()                   
    if nil != err {
        fmt.Println(err)
        return []byte(err.Error())
    }
    fmt.Println("Process PID:", command.Process.Pid)
    err = command.Wait() //等待執行完成
    if nil != err {
        fmt.Println(err)
        return []byte(err.Error())
    }
    fmt.Println("ProcessState PID:", command.ProcessState.Pid())
    return []byte("success")
}
           

mail.go

package main

import (
    "fmt"
    "gopkg.in/gomail.v2"
)

/*
* @Author:15815
* @Date:2019/5/8 17:47
* @Name:mail
* @Function:郵件發送
 */

type Client struct {
    Host     string
    Port     int
    Mail     string
    Password string
}

type SendObject struct {
    ToMails     []string
    CcMails     []string
    Object      string
    ContentType string
    Content     string
}

func NewMailClient(host string, port int, sendMail, password string) *Client {
    return &Client{
        Host:     host,
        Port:     port,
        Mail:     sendMail,
        Password: password,
    }
}
func (m *Client) SendMail(s *SendObject) error {
    mgs := gomail.NewMessage()
    mgs.SetHeader("From", m.Mail)
    mgs.SetHeader("To", s.ToMails...)
    mgs.SetHeader("Cc", s.CcMails...)
    mgs.SetHeader("Subject", s.Object)
    mgs.SetBody(s.ContentType, s.Content)
    d := gomail.NewDialer(m.Host, m.Port, m.Mail, m.Password)
    if err := d.DialAndSend(mgs); err != nil {
        fmt.Printf("send mail err:%v", err)
        return err
    }
    return nil
}
           

ci.sh

#!/bin/bash
funCiTools()
{
    docker pull registry.cn-shanghai.aliyuncs.com/***/***:$1
    docker rm -f $1
    docker run -d -p $2:8000 --name $1 registry.cn-shanghai.aliyuncs.com/***/***:$1
}
funCiTools $1 $2           

第一個參數是對應開發人員啟動容器的名字以及建構鏡像的tag和上面建構的配置一緻,第二個參數是映射的端口。

建構ci_web_server

自己的電腦是windows的,朋友的阿裡雲ecs上又沒有go環境,想建構基于linux的二進制程式,于是直接利用docker image 來建構了鏡像,一個指令解決問題。

build.sh

docker run --rm -i -v `pwd`:/go/src/ci -w /go/src/ci  golang:1.11.5   go build -o ci  ci           

使用nohup直接将ci server放置背景運作。

nohup ./ci  >output 2>&1 &           

效果測試

直接修改點東西送出代碼後就等郵件通知了,是不是感覺很爽。

送出完代碼後就開始觸發建構了。

最後建構完完成後觸發ci server 進行deploy,完成郵件通知。

登入到阿裡雲Ecs上我們發現我們的容器已經啟動成功了。