Quantcast
Channel: 小惡魔 – 電腦技術 – 工作筆記 – AppleBOY
Viewing all 325 articles
Browse latest View live

Go 語言的 graphQL-go 套件正式支援 Concurrent Resolvers

$
0
0
GraphQL_Logo.svg 要在 Go 語言寫 graphQL,大家一定對 graphql-go 不陌生,討論度最高的套件,但是我先說,雖然討論度是最高,但是效能是最差的,如果大家很要求效能,可以先參考此專案,裡面有目前 Go 語言的 graphQL 套件比較效能,有機會在寫另外一篇介紹。最近 graphql-go 的作者把 Concurrent Resolvers 的解法寫了一篇 Issue 來討論,最終採用了 Resolver returns a Thunk 方式來解決 Concurrent 問題,這個 PR 沒有用到額外的 goroutines,使用方式也最簡單
"pullRequests": &graphql.Field{
    Type: graphql.NewList(PullRequestType),
    Resolve: func(p graphql.ResolveParams) (interface{}, error) {
        ch := make(chan []PullRequest)
        // Concurrent work via Goroutines.
        go func() {
            // Async work to obtain pullRequests.
            ch <- pullRequests
        }()
        return func() interface{} {
            return <-ch
        }, nil
    },
},

使用方式

先用一個簡單例子來解釋之前的寫法會是什麼形式
package main

import (
    "encoding/json"
    "fmt"
    "log"
    "time"

    "github.com/graphql-go/graphql"
)

type Foo struct {
    Name string
}

var FieldFooType = graphql.NewObject(graphql.ObjectConfig{
    Name: "Foo",
    Fields: graphql.Fields{
        "name": &graphql.Field{Type: graphql.String},
    },
})

type Bar struct {
    Name string
}

var FieldBarType = graphql.NewObject(graphql.ObjectConfig{
    Name: "Bar",
    Fields: graphql.Fields{
        "name": &graphql.Field{Type: graphql.String},
    },
})

// QueryType fields: `concurrentFieldFoo` and `concurrentFieldBar` are resolved
// concurrently because they belong to the same field-level and their `Resolve`
// function returns a function (thunk).
var QueryType = graphql.NewObject(graphql.ObjectConfig{
    Name: "Query",
    Fields: graphql.Fields{
        "concurrentFieldFoo": &graphql.Field{
            Type: FieldFooType,
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                type result struct {
                    data interface{}
                    err  error
                }
                ch := make(chan *result, 1)
                go func() {
                    defer close(ch)
                    time.Sleep(1 * time.Second)
                    foo := &Foo{Name: "Foo's name"}
                    ch <- &result{data: foo, err: nil}
                }()
                r := <-ch
                return r.data, r.err
            },
        },
        "concurrentFieldBar": &graphql.Field{
            Type: FieldBarType,
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                type result struct {
                    data interface{}
                    err  error
                }
                ch := make(chan *result, 1)
                go func() {
                    defer close(ch)
                    time.Sleep(1 * time.Second)
                    bar := &Bar{Name: "Bar's name"}
                    ch <- &result{data: bar, err: nil}
                }()
                r := <-ch
                return r.data, r.err
            },
        },
    },
})

func main() {
    schema, err := graphql.NewSchema(graphql.SchemaConfig{
        Query: QueryType,
    })
    if err != nil {
        log.Fatal(err)
    }
    query := `
        query {
            concurrentFieldFoo {
                name
            }
            concurrentFieldBar {
                name
            }
        }
    `
    result := graphql.Do(graphql.Params{
        RequestString: query,
        Schema:        schema,
    })
    b, err := json.Marshal(result)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s", b)
    /*
        {
          "data": {
            "concurrentFieldBar": {
              "name": "Bar's name"
            },
            "concurrentFieldFoo": {
              "name": "Foo's name"
            }
          }
        }
    */
}
接著看看需要多少時間來完成執行
$ time go run examples/concurrent-resolvers/main.go | jq
{
  "data": {
    "concurrentFieldBar": {
      "name": "Bar's name"
    },
    "concurrentFieldFoo": {
      "name": "Foo's name"
    }
  }
}

real    0m4.186s
user    0m0.508s
sys     0m0.925s
總共花費了四秒,原因是每個 resolver 都是依序執行,所以都需要等每個 goroutines 執行完成才能進入到下一個 resolver,上面例子該如何改成 Concurrent 呢,很簡單,只要將 return 的部分換成
                return func() (interface{}, error) {
                    r := <-ch
                    return r.data, r.err
                }, nil
執行時間如下
$ time go run examples/concurrent-resolvers/main.go | jq
{
  "data": {
    "concurrentFieldBar": {
      "name": "Bar's name"
    },
    "concurrentFieldFoo": {
      "name": "Foo's name"
    }
  }
}

real    0m1.499s
user    0m0.417s
sys     0m0.242s
從原本的 4 秒多,變成 1.5 秒,原因就是兩個 resolver 的 goroutines 會同時執行,最後才拿結果。

心得

有了這功能後,比較複雜的 graphQL 語法,就可以用此方式加速執行時間。作者也用 Mogodb + graphql 寫了一個範例,大家可以參考看看。

Go 語言專案程式碼品質

$
0
0
Screen Shot 2018-03-17 at 11.40.12 PM 本篇想介紹我在寫開源專案會用到的工具及服務,其實在編譯 Go 語言同時,就已經確保了一次程式碼品質,或者是在編譯之前會跑 go fmtgo vet 的驗證,網路上也蠻多工具可以提供更多驗證,像是:
  • errcheck (檢查是否略過錯誤驗證)
  • unused (檢查沒用到的 func, variable or const)
  • structcheck (檢查 struct 內沒有用到的 field)
  • varcheck (拿掉沒有用到的 const 變數)
  • deadcode (沒有用到的程式碼)
但是這麼多驗證工具,要一一導入專案,實在有點麻煩,我自己在公司內部只有驗證 go fmtgo vetmisspell-check (驗證英文單字是否錯誤) 及 vendor-check (驗證開發者是否有去修改過 vendor 而沒有恢復修正)。如果你有在玩開源專案,其實可以不用這麼麻煩,導入兩套工具就可以讓你安心驗證別人發的 PR。底下來介紹一套工具及另外一套雲端服務。

影片介紹

我錄製了一段影片介紹這兩套工具及服務,不想看本文的可以直接看影片
此影片同步在 Udemy 課程內,如果有購買課程的朋友們,也可以在 Udemy 上面觀看,如果想學習更多 Go 語言教學,現在可以透過 $1800 價格購買。

golangci.com 服務

先說好這套服務對於私有專案是需要付費的,如果是開源專案,請盡情使用,目前只有支援 GitHub 上面的專案為主,不支援像是 GitLab 或 Bitbucket。對於有在寫 Go 開源專案的開發者,務必啟用這服務,此服務幫忙驗證超多檢查,請看底下 Screen Shot 2018-09-20 at 9.36.50 AM 當然不只有幫忙整合 CI/CD 的功能,還會在每個 PR 只要遇到驗證錯誤,直接會有 Bot 留言 Check_if_token_expired_in_MiddlewareFunc_by_a180285_·_Pull_Request__169_·_appleboy_gin-jwt_🔊 非常的方便,假設您的團隊有在 GitHub 使用,強烈建議導入這套服務。另外也可以進入 Repo 列表內看到詳細的錯誤清單。 Report_for_Pull_Request_appleboy_gorush_undefined_🔊

go-critic 工具

go-critic 也是一套檢查程式碼品質的工具,只提供 CLI 方式驗證,不提供雲端整合服務,如果要導入 CI/CD 流程,請自行取用,為什麼特別介紹這套,這套工具其實是在幫助您如何寫出 Best Practice 的 Go 語言程式碼,就算你不打算用這套工具,那推薦壹定要閱讀完驗證清單,這會讓專案的程式碼品質再提升。像是寫 Bool 函式,可能會這樣命名:
func Enabled() bool
用了此工具,會建議寫成 (是不是更好閱讀了)
func IsEnabled() bool
還有很多驗證請自行參考,不過此工具會根據專案的大小來決定執行時間,所以我個人不推薦導入 CI/CD 流程,而是久久可以在自己電腦跑一次,一次性修改全部,這樣才不會影響部署時間。

心得

上面提供的兩套工具及服務,大家如果有興趣,歡迎導入,第一套雲服務我個人都用在開源專案,第二套工具,會用在公司內部專案,但是不會導入在 CI/CD 流程內。

gofight 支援檔案上傳測試

$
0
0
Go-Logo_Blue gofight 是一套用 Go 語言撰寫的 HTTP API 測試套件,之前已經寫過一篇介紹用法,當時候尚未支援檔案上傳測試,也就是假設寫了一個檔案上傳的 http handler 在專案內如何寫測試,底下來看看該如何使用。

準備環境

首先需要一個測試檔案,通常會在專案底下建立 testdata 目錄,裡面放置一個叫 hello.txt 檔案,內容為 world。接著安裝 gofight 套件,可以用團隊內喜愛的 vendor 工具,我個人偏好 govendor
$ govendor fetch github.com/kardianos/govendor
或
$ go get -u github.com/kardianos/govendor

檔案上傳範例

這邊用 gin 框架當作範例,如果您用其他框架只要支援 http.HandlerFunc 都可以使用。
func gintFileUploadHandler(c *gin.Context) {
    ip := c.ClientIP()
    file, err := c.FormFile("test")
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": err.Error(),
        })
        return
    }
    foo := c.PostForm("foo")
    bar := c.PostForm("bar")
    c.JSON(http.StatusOK, gin.H{
        "hello":    "world",
        "filename": file.Filename,
        "foo":      foo,
        "bar":      bar,
        "ip":       ip,
    })
}

// GinEngine is gin router.
func GinEngine() *gin.Engine {
    gin.SetMode(gin.TestMode)
    r := gin.New()
    r.POST("/upload", gintFileUploadHandler)

    return r
}
上面例子可以發現,測試端需要傳兩個 post 參數,加上一個檔案 (檔名為 test),底下看看 gofight 怎麼寫測試。

檔案上傳測試

gofight 現在支援一個函式叫 SetFileFromPath 此 func 支援三個參數。
  1. 檔案讀取路徑
  2. POST 檔案名稱
  3. POST 參數
第一個就是測試檔案要從哪個實體路徑讀取,假設放在 testdata/hello.txt 那該參數就是填寫 testdata/hello.txt,請注意這是相對於 *_test.go 檔案路徑,第二個參數就是定義該檔案的 post 參數,請依照 handler 內的 c.FormFile("test") 來決定變數名稱,第三個參數可有可無,也就是檔案上傳頁面,可能會需要上傳其他 post 參數,這時候就要寫在第三個參數,看底下實際例子:
func TestUploadFile(t *testing.T) {
    r := New()

    r.POST("/upload").
        SetFileFromPath("fixtures/hello.txt", "test", H{
            "foo": "bar",
            "bar": "foo",
        }).
        Run(framework.GinEngine(), func(r HTTPResponse, rq HTTPRequest) {
            data := []byte(r.Body.String())

            hello := gjson.GetBytes(data, "hello")
            filename := gjson.GetBytes(data, "filename")
            foo := gjson.GetBytes(data, "foo")
            bar := gjson.GetBytes(data, "bar")
            ip := gjson.GetBytes(data, "ip")

            assert.Equal(t, "world", hello.String())
            assert.Equal(t, "hello.txt", filename.String())
            assert.Equal(t, "bar", foo.String())
            assert.Equal(t, "foo", bar.String())
            assert.Equal(t, "", ip.String())
            assert.Equal(t, http.StatusOK, r.Code)
            assert.Equal(t, "application/json; charset=utf-8", r.HeaderMap.Get("Content-Type"))
        })
}
可以看到上面例子正確拿到檔案上傳資料,並且測試成功。

心得

其實這類測試 HTTP Handler API 的套件相當多,當時就自幹一套當作練習,後來每個 Go 專案,我個人都用自己寫的這套,測試起來相當方便。更多詳細的用法請直接看 gofight 文件。對於 Go 語言有興趣的朋友們,可以直接參考我的線上課程

Go 語言 1.11 版本推出 go module

$
0
0
Go-Logo_Blue 本篇來聊聊 Go 語言在 1.11 版本推出的 新功能,相信大家也許還不知道此功能是做什麼用的,我們來回顧看看在初學 Go 語言的時候,最令人困擾的就是 GOPATH,所有的專案都必須要在 GOPATH 底下開發,然而在更久前還沒有 Vendor 時候,兩個專案用不同版本的同一個 Package 就必須要使用多個 GOPATH 來解決,但是隨著 Vendor 在 1.5 版的推出,解決了這問題,所以現在只要把專案放在 GOPATH 底下,剩下的 Package 管理都透過 Vendor 目錄來控管,在很多大型開源專案都可以看到把 Vendor 目錄放入版本控制已經是基本的 Best Practice,而 go module 推出最大功能用來解決 GOPATH 問題,也就是未來開發專案,隨意讓開發者 clone 專案到任何地方都可以,另外也統一個 Package 套件管理,不再需要 Vendor 目錄,底下舉個實際例子來說明。

影片介紹


此影片同步在 Udemy 課程內,如果有購買課程的朋友們,也可以在 Udemy 上面觀看,如果想學習更多 Go 語言教學,現在可以透過 $1800 價格購買。

傳統 go vendor 管理

先看個例子:
package main

import (
    "fmt"

    "github.com/appleboy/com/random"
)

func main() {
    fmt.Println(random.String(10))
}
將此專案放在 $GOPATH/src/github.com/appleboy/test 這是大家寫專案必定遵守的目錄規則,而 vendor 管理則會從 PackageManagementTools 選擇一套。底下是用 govendor 來當作例子
$ govendor init
$ govendor fetch github.com/appleboy/com/random
最後用 go build 產生 binary
$ go build -v -o main .
如果您不在 GOPATH 裡面工作,就會遇到底下錯誤訊息:
Error: Package "xxxx" not a go package or not in GOPAT
如果換到 Go 1.11 版本的 module 功能就能永久解決此問題

使用 go module

用 go module 解決兩個問題,第一專案內不必再使用 vendor 管理套件,第二開發者可以任意 clone 專案到任何地方,直接下 go build 就可以拿到執行檔了。底下是使用方式
project
--> main.go
--> main_test.go
初始化專案,先啟動 GO111MODULE 變數,在 go 1.11 預設是 auto
$ export GO111MODULE=on
$ go mod init github.com/appleboy/project
可以看到專案會多出一個 go.mod 檔案,用來記錄使用到的套件版本,如果本身已經在使用 vendor 管理,那麼 mod init 會自動將 vendor 紀錄的版本寫入到 go.mod。接著執行下載
$ go mod download
專案內會多出 go.sum 檔案,其實根本不用執行 go mod download,只要在專案內下任何 go build|test|install 指令,就會自動將 pkg 下載到 GOPATH/pkg/mod
$ tree ~/go/pkg/mod/github.com/
/Users/mtk10671/git/go/pkg/mod/github.com/
└── appleboy
    └── com@v0.0.0-20180410030638-c0b5901f9622
        ├── LICENSE
        ├── Makefile
        ├── README.md
        ├── array
        │   ├── array.go
        │   └── array_test.go
        ├── convert
        │   ├── convert.go
        │   └── convert_test.go
        ├── file
        │   ├── file.go
        │   └── file_test.go
        ├── random
        │   ├── random.go
        │   └── random_test.go
        └── vendor
            └── vendor.json
目前 go module 還在實驗階段,如果升級套件或下載套件有任何問題,請透過底下指令將 pkg 目錄清空就可以了。
$ go clean -i -x -modcache

心得

由於 go module 的出現,現在所有的開源專案都相繼支援,但是又要相容於 1.10 版本之前 (含 1.10),所以變成要維護 go.modvendor 兩種版本。我個人感覺 go module 解決 GOPATH 問題,不再依賴此環境變數,讓想入門 Go 語言的開發者,可以快速融入開發環境。

程式碼範例

用 10 分鐘部署專案到 AWS Lambda

$
0
0
Screen Shot 2018-10-24 at 9.37.49 AM 看到這標題也許非常聳動,也可能覺得不可思議,今天來探討如何將專案直接部署到 AWS Lambda 並且自動化將 API Gateway 設定完成。當然要做到完全自動化,必須要使用一些工具才能完成,本篇將介紹由 TJ 所開發的 apex/up 工具,如果您不熟悉 EC2 也不太懂 Command line 操作,本文非常適合您,不需要管理任何 EC2 機器,也不需要在熟悉任何 Linux Command 就可以完成簡單的專案部署。首先為什麼我選擇 apex/up 而不是選擇 apex/apex,原因是使用 up 工具,您的專案是不用更動任何程式碼,就可以將專案直接執行在 AWS Lambda,那 API Gateway 部分也會一並設定完成,將所有 Request 直接 Proxy 到該 Lambda function。如果您希望對於 AWS Lambda 有更多進階操作,我會建議您用 apex/apexServerless。您可以想像使用 up 就可以將 AWS Lambda 當作小型的 EC2 服務,但是不用自己管理 EC2,現在 up 支援 Golang, Node.js, Python 或 Java 程式語言,用一行 command 就可以將專案部署到雲端了。

影片教學

本系列影片不只有介紹 up 工具,還包含『設定 custom domain 在 API Gateway』及『用 drone 搭配 apex/up 自動化部署 AWS Lambda』。有興趣可以參考底下:
  • 使用 apex/up 工具部署 Go 專案到 AWS Lambda Youtube, Udemy
  • 設定 Custom Domain Names 在 API Gateway 上 Udemy
  • 用 drone-apex-up 自動化更新 Go 專案到 AWS Lambda Udemy
買了結果沒興趣想退費怎麼辦?沒關係,在 Udemy 平台 30 天內都可以全額退費,所以不用擔心買了後悔。如果你對 Go 語言 (現在 $1800) 及 Drone 自動化部署 (現在 $1800) 都有興趣,想要一起購買,你可以直接匯款到底下帳戶,有合購優惠價
  • 富邦銀行: 012
  • 富邦帳號: 746168268370
  • 匯款金額: 台幣 $3400 元

使用 up 工具

up 可以在幾秒鐘的時間將專案直接部署到 AWS Lambda,透過 up 可以快速將 Staging 及 Productoon 環境建置完成。底下直接用 GO 語言當例子。
package main

import (
    "os"

    "github.com/gin-gonic/gin"
)

func main() {
    port := ":" + os.Getenv("PORT")
    stage := os.Getenv("UP_STAGE")

    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong " + stage,
        })
    })

    r.GET("/v1", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong " + stage + " v1 ++ drone",
        })
    })

    r.Run(port)
}
接著在專案內放置 up.json 檔案,內容如下:
{
  "name": "demo",
  "profile": "default",
  "regions": [
    "ap-southeast-1"
  ]
}
name 代表 aws lambda 函數名稱,profile 會讀 ~/.aws/credentials 底下的 profile 設定。接著執行 up -v
$ up -v
up_json_—_training 從上圖可以看到預設編譯行為是
$ GOOS=linux GOARCH=amd64 go build -o server *.go
並且上傳完成後會將 server 移除。登入 AWS Lambda 入口,可以看到 up 幫忙建立了兩個環境,一個是 staging 另一個是 production,假設要部署專案到 production 環境可以下
$ up deploy production -v
部署完成後,可以直接透過 up 拿到 API Gateway 給的測試 URL,可以在瀏覽器瀏覽
$ up url
https://xxxxxxx.execute-api.ap-southeast-1.amazonaws.com/staging/
當然也可以到 API Gateway 那邊設定好 Custom Domain 就可以直接用自己的 Domain,而不會有 /staging/ 字眼在 URL 路徑上。

搭配 Drone 自動化部署

在自己電腦測試完成後,接著要設定 CI/CD 來達到自動化部署,本文直接拿 Drone 來串接。底下是 .drone.yml 設定檔
pipeline:
  build:
    image: golang:1.11
    pull: true
    environment:
      TAGS: sqlite
      GO111MODULE: "on"
    commands:
      - cd example23-deploy-go-application-with-up && GOOS=linux GOARCH=amd64 go build -o server *.go
  up:
    image: appleboy/drone-apex-up
    pull: true
    secrets: [aws_secret_access_key, aws_access_key_id]
    stage:
      - staging
    directory: ./example23-deploy-go-application-with-up
    when:
      event: push
      branch: master

  up:
    image: appleboy/drone-apex-up
    pull: true
    secrets: [aws_secret_access_key, aws_access_key_id]
    stage:
      - production
    directory: ./example23-deploy-go-application-with-up
    when:
      event: tag
上面可以很清楚看到,只要是 push 到 master branch 就會觸發 staging 環境部署。而下 Tag 則是部署到 Production。要注意的是由於 up 會有預設編譯行為,但是專案複雜的話就需要透過其他指令去執行。只要去蓋掉預設行為就可以。
{
  "name": "demo",
  "profile": "default",
  "regions": [
    "ap-southeast-1"
  ],
  "hooks": {
    "build": [
      "up version"
    ],
    "clean": [
    ]
  }
}
看到 hooks 階段,其中 build 部分需要填寫,不可以是空白。

心得

如果您想快速的架設好 API 後端,或者是靜態網站,我相信 up 是一套不錯的工具,可以快速架設好開發或測試環境。而且可以省下不少開 EC2 費用,如果有興趣的話大家可以參考看看 up 工具。

高雄 Mopcon 濁水溪以南最大研討會 – Drone CI/CD 介紹

$
0
0
Screen Shot 2018-11-06 at 1.16.22 PM 今年又以講者身份參加 Mopcon 南區最大研討會,此次回高雄最主要推廣 Drone 這套 CI/CD 平台。大家可以從過去的 Blog 或影片可以知道我在北部推廣了很多次 Drone 開源軟體,唯獨南台灣還沒講過,所以透過 Mopcon 研討會終於有機會可以來推廣了。本次把 Drone 的架構圖畫出來,如何架設在 Kubernetes 上以及該如何擴展 drone agent,有興趣的可以參考底下投影片:

投影片

很高興 Drone 在今年宣佈支援 ARM 及 Windows 兩大平台,而我也正在把一些 drone 的 plugin 支援 ARM 及 Windows,相信在 Drone 0.9 版正式推出後,就可以讓 Windows 使用者架設在 Microsoft Azure

Flutter 推出 1.0 版本

$
0
0
Screen Shot 2018-12-05 at 10.25.34 AM 很高興看到台灣時間 12/5 號 Flutter 正式推出 1.0 版本,相信很多人都不知道什麼是 Flutter,簡單來說開發者只要學會 Flutter 就可以維護一套程式碼,並且同時編譯出 iOS 及 Android 手機 App,其實就跟 Facebook 推出的 React Native 一樣,但是 Flutter 的老爸是 Google。相信大家很常看到這一兩年內,蠻多新創公司相繼找 RN 工程師,而不是分別找兩位 iOS 及 Android 工程師,原因就在後續的維護性及成本。而 Flutter 也有相同好處。我個人覺得 RN 跟 Flutter 比起來,單純對入門來說,RN 是非常好上手的,但是如果您考慮到後續的維護成本,我建議選用 Flutter,雖然 Flutter 要學一套全新的語言 Dart,在初期時要學習如何使用 Widgets,把很多元件都寫成 Widgets 方便後續維護。但是在 RN 後期的維護使用了大量的第三方 Library,您想要升級一個套件可能影響到太多地方,造成不好維護。語言選擇 RN 可以使用純 JavaScript 撰寫,或者是導入 JS Flow + TypeScript 來達到 Statically Type,而 Flutter 則是使用 Dart 直接支援強型別編譯。如果現在要我選擇學 RN 或 Flutter 我肯定選擇後者。那底下來看看這次 Flutter 釋出了哪些新功能?對於 Flutter 還不了解的,可以看底下介紹影片。

Flutter 1.0

Flutter 在 1.0 版本使用了最新版 Dart 2.1 版本,那在 Dart 2 版本帶來什麼好處?此版本提供了更小的 code size,快速檢查型別及錯誤型別的可用性。這次的 Rlease 也代表之後不會再更動版本這麽快了,可以看看在 GitHub 上 Release 速度,在 1.0 還沒出來前,大概不到一週就會 Release 一版。未來應該不太會動版這麼迅速了。當然還有其他功能介紹像是 Add to AppPlatform Views 會預計在 2019 二月正式跟大家見面。詳細介紹可以參考 Flutter 1.0: Google’s Portable UI Toolkit

Square SDK

Square 釋出了兩套 SDK,幫助 Flutter 開發者可以快速整合手機支付,或者是直接透過 Reader 讀取手機 App 資料付款兩種方式。詳細使用方式可以參考 Flutter plugin for Reader SDKFlutter plugin for In-App Payments SDK

Flare 2D 動畫

Flutter 釋出 Flare 讓 Designer 可以快速的在 Fluter 產生動畫,這樣可以透過 Widget 快速使用動畫。所以未來 Designer 跟 Developer 可以加速 App 實作。這對於兩種不同領域的工程師是一大福音啊。

CI/CD 流程

相信大家最困擾的就是如何在 Android 及 iOS 自動化測試及同時發佈到 App StoreGoogle Play,好的 Flutter 聽到大家的聲音了,一個 Flutter 合作夥伴 Nevercode 建立一套 Codemagic,讓開發者可以寫一套 code base 自動在 iOS 及 Android 上面測試,並且同時發佈到 Apple 及 Google,減少之前很多手動流程,此套工具還在 Beta 版本,目前尚未看到收費模式。想試用的話,可以直接在 GitHub 上面建立 Flutter 專案。登入之後選取該專案,每次 commit + push 後就可以看到正在測試及部署了。 Screen Shot 2018-12-05 at 11.32.40 AM

Hummingbird

Hummingbird 是 Flutter runtime 用 web-base 方式實作,也就是說 Flutter 不只有支援原生 ARM Code 而也支援 JavaScript,未來也可以透過 Flutter 直接產生 Web 相關程式碼,開發者不用改寫任何一行程式碼,就可以直接將 Flutter 運行在瀏覽器內。詳細情形可以直接看官方部落格,在明年 Google I/O 也會正式介紹這門技術。

後記

更多詳細的影片可以參考 flutter live 18

Drone CI/CD 推出 Cloud 服務支援開源專案

$
0
0
Screen Shot 2018-12-08 at 10.36.20 PM Drone 在上個月宣布推出 Cloud 服務 整合 GitHub 帳戶內的 Repo,只要登入後就可以跑 GitHub 上面的專案自動化整合及測試,原先在 GitHub 上面常見的就是 TravisCircleCI,現在 Drone 也正式加入角逐行列,但是從文中內可以看到其實是由 Packet 這間公司獨家贊助硬體及網路給 Drone,兩台實體機器,一台可以跑 X86 另外一台可以跑 ARM,也就是如果有在玩 ARM 開發版,現在可以直接在 Drone Cloud 上面直接跑測試。底下是硬體規格: Screen Shot 2018-12-09 at 7.16.54 PM 最後大家一定會問 Drone 有支援 Matrix Builds 嗎?答案就是有的,也就是專案可以同時測試多版本的語言,或多版本的資料庫,甚至同時測試 X86 及 ARM 系統,這些功能在 Drone 都是有支援的。詳細設定方式可以參考『Multi-Platform Pipelines』。

影片介紹

Drone Cloud 的簡介可以參考底下影片 Drone 新版 UI 介紹可以看底下影片:

將 Flickr 相簿備份到 Google Photos

$
0
0

Flickr2019 年一月會開始將免費會員照片刪除到剩下 1000 張,這次透過 Go 工具 來將備份好的 Flickr 相簿上傳到 Google Photos,此工具只適合用在 MacOS 及 Linux 上面,Windows 請改用『Backup and Sync from Google』工具。

*Free members with more than 1,000 photos or videos uploaded to Flickr have until Tuesday, January 8, 2019, to upgrade to Pro or download content over the limit. After January 8, 2019, members over the limit will no longer be able to upload new photos to Flickr. After February 5, 2019, free accounts that contain over 1,000 photos or videos will have content actively deleted — starting from oldest to newest date uploaded — to meet the new limit.

影片介紹

底下影片會帶大家一步一步將 Flickr 檔案備份到 Google Photos 服務上。

用 Docker 整合測試 Flutter 框架

$
0
0
Flutter 是一套以 Dart 語言為主體的手機 App 開發框架,讓開發者可以寫一種語言產生 iOS 及 Android,只要裝好 Flutter 框架,就可以在個人電腦上面同時測試 iOS 及 Android 流程,如果您需要 Docker 環境,可以直接參考此開源專案,裡面已經將 Flutter 1.0 SDK 包在容器,只要將專案目錄掛載到 Docker 內,就可以透過 flutter test 指令來完成測試,對於 CI/CD 流程使用 Docker 技術非常方便。

線上影片教學

Docker 使用方式

下載 Docker Image,檔案有點大,先下載好比較方便
$ docker pull appleboy/flutter-docker:1.0.0
下載測試範例,並執行測試
$ git clone https://github.com/appleboy/flutter-demo.git
$ docker run -ti -v ${PWD}/flutter-demo:/flutter-demo -w /flutter-demo \
  appleboy/flutter-docker:1.0.0 \
  /bin/sh -c "flutter test"

使用 Drone 自動化測試

搭配 Drone Cloud 服務,在專案底下新增 .drone.yml,內容如下:
kind: pipeline
name: testing

steps:
  - name: flutter
    image: appleboy/flutter-docker:1.0.0
    commands:
      - flutter test

Go Module 導入到專案內且搭配 Travis CI 或 Drone 工具

$
0
0

相信各位 Go 語言開發者陸陸續續都將專案從各種 Vendor 工具轉換到 Go Module,本篇會帶大家一步一步從舊專案轉換到 Go Module,或是該如何導入新專案,最後會結合 CI/CD 著名的兩套工具 TravisDrone 搭配 Go Module 測試。

影片介紹

  1. 舊專案內 vendor 轉換成 go module 設定 (1:15)
  2. 新專案如何啟用 go module (6:20)
  3. 在 Travis CI 或 Drone 如何使用 go module (8:31)
  4. 在開源專案內並存 vendor 及 go module (介紹 Gin 如何使用 vendor 及 go module) (15:00)

更多實戰影片可以參考我的 Udemy 教學系列

舊專案

假設原本的專案有導入 vendor 工具類似 govendordep,可以在目錄底下找到 vendor/vendor.jsonGopkg.toml,這時候請在專案目錄底下執行

$ go mod init github.com/appleboy/drone-line
$ go mod download

您會發現 go module 會從 vendor/vendor.jsonGopkg.toml 讀取相關套件資訊,接著寫進去 go.mod 檔案,完成後可以下 go mod dowload 下載所有套件到 $HOME/go/pkg/mod

新專案

新專案只需要兩個步驟就可以把相關套件設定好

$ go mod init github.com/appleboy/drone-line
$ go mod tidy

其中 tidy 可以確保 go.modgo.sum 裡面的內容都跟專案內所以資料同步,假設在程式碼內移除了 package,這樣 tidy 會確保同步性移除相關 package。

整合 Travis 或 Drone

go module 在 1.11 版本預設是不啟動的,那在 Travis 要把 GO111MODULE 環境變數打開

matrix:
  fast_finish: true
  include:
  - go: 1.11.x
    env: GO111MODULE=on

完成後可以到 Travis 的環境看到底下 go get 紀錄

而在 Drone 的設定如下:

steps:
  - name: testing
    image: golang:1.11
    pull: true
    environment:
      GO111MODULE: on
    commands:
      - make vet
      - make lint
      - make misspell-check
      - make fmt-check
      - make build_linux_amd64
      - make test

結論

在開源專案內為了相容 Go 舊版本,所以 Gin 同時支援了 govendor 及 go module,其實還蠻難維護的,但是可以透過 travis 環境變數的判斷來達成目的:

language: go
sudo: false
go:
  - 1.6.x
  - 1.7.x
  - 1.8.x
  - 1.9.x
  - 1.10.x
  - 1.11.x
  - master

matrix:
  fast_finish: true
  include:
  - go: 1.11.x
    env: GO111MODULE=on

git:
  depth: 10

before_install:
  - if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi

install:
  - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; else make install; fi
  - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi
  - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi

詳細設定請參考 .travis 設定

用 Traefik 搭配 Docker 快速架設服務

$
0
0

更新: 2019.01.10 新增教學影片

drone traefik docker deploy

相信大家在架設服務肯定會選一套像是 HAProxy, Nginx, ApacheCaddy,這四套架設的難度差不多,如果要搭配 Let’s Encrypt 前面兩套需要自己串接 (Nginx, Apache),而 Caddy 是用 Golang 開發裡面已經內建了 Let’s Encrypt,,管理者不用擔心憑證過期,相當方便。但是本篇我要介紹另外一套工具叫 Traefik,這一套也是用 Go 語言開發,而我推薦這套的原因是,此套可以跟 Docker 很深度的結合,只要服務跑在 Docker 上面,Traefik 都可以自動偵測到,並且套用設定。透過底下的範例讓 Traefik 串接後端兩個服務,分別是 domain1.comdomain2.com。來看看如何快速設定 Traefik。

traefik + docker + golang

影片教學

不想看內文的,可以直接參考 Youtube 影片,如果喜歡的話歡迎訂閱

撰寫服務

我們先透過底下 Go 語言實作後端,並且放到 Docker Hub 內,方便之後透過 docker-compose 設定。

package main

import (
    "flag"
    "fmt"
    "log"
    "net/http"
    "os"
    "time"
)

// HelloWorld for hello world
func HelloWorld() string {
    return "Hello World, golang workshop!"
}

func handler(w http.ResponseWriter, r *http.Request) {
    log.Printf("Got http request. time: %v", time.Now())
    fmt.Fprintf(w, "I love %s!", r.URL.Path[1:])
}

func pinger(port string) error {
    resp, err := http.Get("http://localhost:" + port)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    if resp.StatusCode != 200 {
        return fmt.Errorf("server returned not-200 status code")
    }

    return nil
}

func main() {
    var port string
    var ping bool
    flag.StringVar(&port, "port", "8080", "server port")
    flag.StringVar(&port, "p", "8080", "server port")
    flag.BoolVar(&ping, "ping", false, "check server live")
    flag.Parse()

    if p, ok := os.LookupEnv("PORT"); ok {
        port = p
    }

    if ping {
        if err := pinger(port); err != nil {
            log.Printf("ping server error: %v\n", err)
        }

        return
    }

    http.HandleFunc("/", handler)
    log.Println("http server run on " + port + " port")
    log.Fatal(http.ListenAndServe(":"+port, nil))
}

撰寫 Dockerfile

FROM alpine:3.8

LABEL maintainer="Bo-Yi Wu <appleboy.tw@gmail.com>" \
  org.label-schema.name="Drone Workshop" \
  org.label-schema.vendor="Bo-Yi Wu" \
  org.label-schema.schema-version="1.0"

RUN apk add --no-cache ca-certificates && \
  rm -rf /var/cache/apk/*

ADD release/linux/i386/helloworld /bin/

ENTRYPOINT ["/bin/helloworld"]

設定 Drone 自動上傳到 DockerHub,使用 drone-docker 外掛。

  - name: publish
    image: plugins/docker:17.12
    settings:
      repo: appleboy/test
      auto_tag: true
      dockerfile: Dockerfile.alpine
      default_suffix: alpine
      username:
        from_secret: docker_username
      password:
        from_secret: docker_password
    when:
      event:
        - push
        - tag

其中 docker_usernamedocker_password 可以到 drone 後台設定。

啟動 Traefik 服務

如果只是單純綁定在非 80 或 443 port,您可以用一般帳號設定 Traefik,設定如下:

debug = false

logLevel = "INFO"
defaultEntryPoints = ["http"]

[entryPoints]
  [entryPoints.http]
  address = ":8080"

[retry]

################################################################
# Docker Provider
################################################################

# Enable Docker Provider.
[docker]

# Docker server endpoint. Can be a tcp or a unix socket endpoint.
#
# Required
#
endpoint = "unix:///var/run/docker.sock"

# Enable watch docker changes.
#
# Optional
#
watch = true

上面設定可以看到將 Traefik 啟動在 8080 port,並且啟動 Docker Provider,讓 Traefik 可以自動偵測目前 Docker 啟動了哪些服務。底下是啟動 Traefik 的 docker-compose 檔案

version: '2'

services:
  traefik:
    image: traefik
    restart: always
    ports:
      - 8080:8080
      # - 80:80
      # - 443:443
    networks:
      - web
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik.toml:/traefik.toml
      # - ./acme.json:/acme.json
    container_name: traefik

networks:
  web:
    external: true

啟動 Traefik 環境前需要建立虛擬網路名叫 web

$ docker network create web
$ docker-compose up -d

啟動 App 服務

接著只要透過 docker-compose 來啟動您的服務

version: '3'

services:
  app_1:
    image: appleboy/test:alpine
    restart: always
    networks:
      - web
    logging:
      options:
        max-size: "100k"
        max-file: "3"
    labels:
      - "traefik.docker.network=web"
      - "traefik.enable=true"
      - "traefik.basic.frontend.rule=Host:domain1.com"
      - "traefik.basic.port=8080"
      - "traefik.basic.protocol=http"

  app_2:
    image: appleboy/test:alpine
    restart: always
    networks:
      - web
    logging:
      options:
        max-size: "100k"
        max-file: "3"
    labels:
      - "traefik.docker.network=web"
      - "traefik.enable=true"
      - "traefik.basic.frontend.rule=Host:domain2.com"
      - "traefik.basic.port=8080"
      - "traefik.basic.protocol=http"

networks:
  web:
    external: true

大家可以清楚看到透過設定 docker label 可以讓 Traefik 自動偵測到系統服務

    labels:
      - "traefik.docker.network=web"
      - "traefik.enable=true"
      - "traefik.basic.frontend.rule=Host:domain2.com"
      - "traefik.basic.port=8080"
      - "traefik.basic.protocol=http"

其中 traefik.basic.frontend.rule 可以填入網站 DNS Name,另外 traefik.basic.port=8080 則是服務預設啟動的 port (在 Go 語言內實作)。

驗證網站是否成功

$ curl -v http://domain1.com:8080/test
$ curl -v http://domain2.com:8080/test

bash screen

搭配 Let’s Encrypt

這邊又要感謝 Go 語言內建 Let’s Encrypt 套件,讓 Go 開發者可以快速整合憑證,這邊只需要修正 Traefik 服務設定檔

logLevel = "INFO"
defaultEntryPoints = ["https","http"]

[entryPoints]
  [entryPoints.http]
  address = ":80"
    [entryPoints.http.redirect]
    entryPoint = "https"
  [entryPoints.https]
  address = ":443"
  [entryPoints.https.tls]

[retry]

[docker]
endpoint = "unix:///var/run/docker.sock"
watch = true
exposedByDefault = false

[acme]
email = "appleboy.tw@gmail.com"
storage = "acme.json"
entryPoint = "https"
onHostRule = true
[acme.httpChallenge]
entryPoint = "http"

跟之前 Traefik 比較多了 entryPointsacme,另外在 docker-compose 內要把 80 及 443 port 啟動,並且將 acme.json 掛載進去

version: '2'

services:
  traefik:
    image: traefik
    restart: always
    ports:
      - 80:80
      - 443:443
    networks:
      - web
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik.toml:/traefik.toml
      - ./acme.json:/acme.json
    container_name: traefik

networks:
  web:
    external: true

其中先建立 acme.json 並且設定權限為 600

$ touch acme.json
$ chmod 600 acme.json

再重新啟動 Traefik 服務

$ docker-compose down
$ docker-compose up -d

最後只要改 traefik.basic.frontend.rule 換成真實的 Domain,你會發現 Traefik 會將憑證內容寫入 acme.json。這也為什麼我們需要將 acme.json 建立在 Host 空間上。

搭配 Drone 自動化更新服務

未來所有服務都可以透過 docker-compose 來啟動,所以只要透過 Drone 將 一些 yaml 設定檔案傳到服務器即可

  - name: scp
    image: appleboy/drone-scp
    pull: true
    settings:
      host: demo1.com
      username: deploy
      key:
        from_secret: deploy_key
      target: /home/deploy/gobento
      rm: true
      source:
        - release/*
        - Dockerfile
        - docker-compose.yml

上面將檔案丟到遠端機器後,再透過 ssh 編譯並且部署

  - name: ssh
    image: appleboy/drone-ssh
    pull: true
    settings:
      host: console.gobento.co
      user: deploy
      key:
        from_secret: deploy_key
      target: /home/deploy/demo
      rm: true
      script:
        - cd demo && docker-compose build
        - docker-compose up -d --force-recreate --no-deps demo
        - docker images --quiet --filter=dangling=true | xargs --no-run-if-empty docker rmi -f

心得

本篇教大家一步一步建立 Traefik 搭配 Docker,相對於 Nginx 我覺得簡單非常多,尤其時可以在 docker-compose 內設定 docker Label,而 Traefik 會自動偵測設定,並且重新啟動服務。希望這篇對於想要快速架設網站的開發者有幫助。如果您有在用 AWS 服務,想省錢可以使用 Traefik 幫您省下一台 ALB 或 ELB 的費用。最後補充一篇效能文章:『NGINX、HAProxy和Traefik负载均衡能力对比』有興趣可以參考一下。

Seagate 2.5吋 4TB 外接硬碟 好市多 2699 元

$
0
0

Seagate 2.5吋 4TB 外接硬碟 好市多 2699 元

本週 Costco 正在特價 Seagate 4TB 外接硬碟特價 2699 元,本來只有 3TB 特價 2699 元活動,後來廠商供應數量不夠,只好拿 4TB 一起來特價 2699 元,所以我去賣場發現一樣價錢,但是足足多了 1 TB 容量啊,只有在本週特價喔 01/18 ~ 01/27 數量有限,賣完就沒了。目前 costco 線上版本已經賣完 4TB 了,大家有需要的可以快去現場看看。

硬碟數據測試

拿到硬碟後,拿 Blackmagic Disk Speed Test 測試一下讀寫速度,看起來還OK,買來當存照片或者是一些私人物品 D 曹。

開箱

Traefik 搭配 Docker 自動更新 Let’s Encrypt 憑證

$
0
0

之前寫過蠻多篇 Let’s Encrypt 的使用教學,但是這次要跟大家介紹一套非常好用的工具 Traefik 搭配自動化更新 Let’s Encrypt 憑證,為什麼會推薦 Traefik 呢,原因在於 Traefik 可以自動偵測 Docker 容器內的 Label 設定,並且套用設定在 Traefik 服務內,也就是只要修改服務的 docker-compose 內容,重新啟動,Traefik 就可以抓到新的設定。這點在其它工具像是 NginxCaddy 是無法做到的。底下我們來一步一步教大家如何設定啟用前後端服務。全部程式碼都放在 GitHub 上面了。

教學影片

啟動 Traefik 服務

在啟動 Traefik 服務前,需要建立一個獨立的 Docker 網路,請在 Host 內下

$ docker network create web

接著建立 Traefik 設定檔存放目錄 /opt/traefik 此目錄自由命名。

$ mkdir -p /opt/traefik

接著在此目錄底下建立三個檔案

$ touch /opt/traefik/docker-compose.yml
$ touch /opt/traefik/acme.json && chmod 600 /opt/traefik/acme.json
$ touch /opt/traefik/traefik.toml

其中 docker-compose.yml 用來啟動 Traefik 服務,acme.json 則是存放 Let’s Encrypt 的憑證,此檔案權限必須為 600,最後則是 traefik 設定檔 traefik.toml。一一介紹這些檔案的內容,底下是 docker-compose.yml

version: '2'

services:
  traefik:
    image: traefik
    restart: always
    ports:
      - 80:80
      - 443:443
    networks:
      - web
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /opt/traefik/traefik.toml:/traefik.toml
      - /opt/traefik/acme.json:/acme.json
    container_name: traefik

networks:
  web:
    external: true

此檔案必須要由 root 使用者來執行,原因是要 Listen 80 及 443 連接埠,其中 acme.json 及 traefik.toml 則由 host 檔案直接掛載進容器內。接著看 traefik.toml

debug = false

logLevel = "INFO"
defaultEntryPoints = ["https","http"]

[entryPoints]
  [entryPoints.http]
  address = ":80"
    [entryPoints.http.redirect]
    entryPoint = "https"
  [entryPoints.https]
  address = ":443"
  [entryPoints.https.tls]

[acme]
email = "xxxxx@gmail.com"
storage = "acme.json"
entryPoint = "https"
onHostRule = true

[acme.httpChallenge]
entryPoint = "http"

[docker]
endpoint = "unix:///var/run/docker.sock"
watch = true

其中 onHostRule 用於讀取 docker container 內的 frontend.ruleHost 設定,這樣才可以跟 Let’s Encrypt 申請到憑證。最後啟動步驟

$ cd /opt/traefik
$ docker-compose up -d

啟動 App 服務

請打開 docker-compose.yml 檔案

version: '3'

services:
  app_1:
    image: appleboy/test:alpine
    restart: always
    networks:
      - web
    logging:
      options:
        max-size: "100k"
        max-file: "3"
    labels:
      - "traefik.docker.network=web"
      - "traefik.enable=true"
      - "traefik.basic.frontend.rule=Host:demo1.ggz.tw"
      - "traefik.basic.port=8080"
      - "traefik.basic.protocol=http"

  app_2:
    image: appleboy/test:alpine
    restart: always
    networks:
      - web
    logging:
      options:
        max-size: "100k"
        max-file: "3"
    labels:
      - "traefik.docker.network=web"
      - "traefik.enable=true"
      - "traefik.basic.frontend.rule=Host:demo2.ggz.tw"
      - "traefik.basic.port=8080"
      - "traefik.basic.protocol=http"

networks:
  web:
    external: true

可以看到透過 docker labels 設定讓 traefik 直接讀取並且套用設定。啟動服務後可以看到 acme.json 已經存放了各個 host 的憑證資訊,未來只要將此檔案備份,就可以不用一直申請了。最後用 curl 來測試看看

心得

由於 Traefik 可以自動讀取 docker label 內容,未來只需要維護 App 的 docker-compose 檔案,對於部署上面相當方便啊,透過底下指令就可以重新啟動容器設定

$ docker-compose up -d --force-recreate --no-deps app

如果對於自動化部署有興趣,可以參考我在 Udemy 上的線上課程

有效率的用 jsonnet 撰寫 Drone CI/CD 設定檔

$
0
0

Jsonnet + Drone

Drone 在 1.0 版本推出了用 jsonnet 來撰寫 YAML 設定檔,方便開發者可以維護多個專案設定。不知道大家有無遇過在啟動新的專案後,需要從舊的專案複製設定到新專案,或者是在 .drone.yml 內有非常多重複性的設定,假設 Go 語言的開源專案需要將執行檔包成 ARM64 及 AMD64 的映像檔,並且上傳到 Docker Hub,底下是 AMD64 的設定檔範例。剛好在 Udemy 課程內有學員詢問到相關問題

---
kind: pipeline
name: linux-arm64

platform:
  os: linux
  arch: arm64

steps:
- name: build-push
  pull: always
  image: golang:1.11
  commands:
  - "go build -v -ldflags \"-X main.build=${DRONE_BUILD_NUMBER}\" -a -o release/linux/arm64/drone-discord"
  environment:
    CGO_ENABLED: 0
    GO111MODULE: on
  when:
    event:
    - push
    - pull_request

- name: build-tag
  pull: always
  image: golang:1.11
  commands:
  - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}\" -a -o release/linux/arm64/drone-discord"
  environment:
    CGO_ENABLED: 0
    GO111MODULE: on
  when:
    event:
    - tag

- name: executable
  pull: always
  image: golang:1.11
  commands:
  - ./release/linux/arm64/drone-discord --help

- name: dryrun
  pull: always
  image: plugins/docker:linux-arm64
  settings:
    dockerfile: docker/Dockerfile.linux.arm64
    dry_run: true
    password:
      from_secret: docker_password
    repo: appleboy/drone-discord
    tags: linux-arm64
    username:
      from_secret: docker_username
  when:
    event:
    - pull_request

- name: publish
  pull: always
  image: plugins/docker:linux-arm64
  settings:
    auto_tag: true
    auto_tag_suffix: linux-arm64
    dockerfile: docker/Dockerfile.linux.arm64
    password:
      from_secret: docker_password
    repo: appleboy/drone-discord
    username:
      from_secret: docker_username
  when:
    event:
    - push
    - tag

trigger:
  branch:
  - master

大家可以看到上面總共快 80 行,如果要再支援 ARM 64,這時候就需要重新複製再貼上,並且把相關設定改掉,有沒有覺得這樣非常難維護 .drone.yml。Drone 的作者聽到大家的聲音了,在 1.0 版本整合了 jsonnet 這套 Data Templating Language,讓您可以寫一次代碼並產生出好幾種環境。底下簡單看一個例子:

// A function that returns an object.
local Person(name='Alice') = {
  name: name,
  welcome: 'Hello ' + name + '!',
};
{
  person1: Person(),
  person2: Person('Bob'),
}

透過 jsonnet 指令可以轉換如下:

{
  "person1": {
    "name": "Alice",
    "welcome": "Hello Alice!"
  },
  "person2": {
    "name": "Bob",
    "welcome": "Hello Bob!"
  }
}

那該如何改 drone 設定檔方便未來多個專案一起維護呢?

影片教學

安裝 drone CLI 執行檔

請直接參考官方文件就可以了,這邊不再詳細介紹,底下是 Mac 範例 (安裝的是 Drone v1.0.5):

$ curl -L https://github.com/drone/drone-cli/releases/download/v1.0.5/drone_darwin_amd64.tar.gz | tar zx
$ sudo cp drone /usr/local/bin

安裝完成後,還需要設定環境變數,才可以跟您的 Drone 伺服器溝通。

$ drone
NAME:
   drone - command line utility

USAGE:
   drone [global options] command [command options] [arguments...]

VERSION:
   1.0.5

COMMANDS:
     build      manage builds
     cron       manage cron jobs
     log        manage logs
     encrypt    encrypt a secret
     exec       execute a local build
     info       show information about the current user
     repo       manage repositories
     user       manage users
     secret     manage secrets
     server     manage servers
     queue      queue operations
     autoscale  manage autoscaling
     fmt        format the yaml file
     convert    convert legacy format
     lint       lint the yaml file
     sign       sign the yaml file
     jsonnet    generate .drone.yml from jsonnet
     plugins    plugin helper functions

撰寫 .drone.jsonnet 檔案

在專案目錄內放置 .drone.jsonnet 檔案,拿 Go 專案當範例:

  1. 驗證程式碼品質
  2. 編譯執行檔
  3. 包成 Docker 容器
  4. 上傳到 Docker Hub
  5. 消息通知
local PipelineTesting = {
  kind: "pipeline",
  name: "testing",
  platform: {
    os: "linux",
    arch: "amd64",
  },
  steps: [
    {
      name: "vet",
      image: "golang:1.11",
      pull: "always",
      environment: {
        GO111MODULE: "on",
      },
      commands: [
        "make vet",
      ],
    },
    {
      name: "lint",
      image: "golang:1.11",
      pull: "always",
      environment: {
        GO111MODULE: "on",
      },
      commands: [
        "make lint",
      ],
    },
    {
      name: "misspell",
      image: "golang:1.11",
      pull: "always",
      environment: {
        GO111MODULE: "on",
      },
      commands: [
        "make misspell-check",
      ],
    },
    {
      name: "test",
      image: "golang:1.11",
      pull: "always",
      environment: {
        GO111MODULE: "on",
        WEBHOOK_ID: { "from_secret": "webhook_id" },
        WEBHOOK_TOKEN: { "from_secret": "webhook_token" },
      },
      commands: [
        "make test",
        "make coverage",
      ],
    },
    {
      name: "codecov",
      image: "robertstettner/drone-codecov",
      pull: "always",
      settings: {
        token: { "from_secret": "codecov_token" },
      },
    },
  ],
  trigger: {
    branch: [ "master" ],
  },
};

local PipelineBuild(os="linux", arch="amd64") = {
  kind: "pipeline",
  name: os + "-" + arch,
  platform: {
    os: os,
    arch: arch,
  },
  steps: [
    {
      name: "build-push",
      image: "golang:1.11",
      pull: "always",
      environment: {
        CGO_ENABLED: "0",
        GO111MODULE: "on",
      },
      commands: [
        "go build -v -ldflags \"-X main.build=${DRONE_BUILD_NUMBER}\" -a -o release/" + os + "/" + arch + "/drone-discord",
      ],
      when: {
        event: [ "push", "pull_request" ],
      },
    },
    {
      name: "build-tag",
      image: "golang:1.11",
      pull: "always",
      environment: {
        CGO_ENABLED: "0",
        GO111MODULE: "on",
      },
      commands: [
        "go build -v -ldflags \"-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}\" -a -o release/" + os + "/" + arch + "/drone-discord",
      ],
      when: {
        event: [ "tag" ],
      },
    },
    {
      name: "executable",
      image: "golang:1.11",
      pull: "always",
      commands: [
        "./release/" + os + "/" + arch + "/drone-discord --help",
      ],
    },
    {
      name: "dryrun",
      image: "plugins/docker:" + os + "-" + arch,
      pull: "always",
      settings: {
        dry_run: true,
        tags: os + "-" + arch,
        dockerfile: "docker/Dockerfile." + os + "." + arch,
        repo: "appleboy/drone-discord",
        username: { "from_secret": "docker_username" },
        password: { "from_secret": "docker_password" },
      },
      when: {
        event: [ "pull_request" ],
      },
    },
    {
      name: "publish",
      image: "plugins/docker:" + os + "-" + arch,
      pull: "always",
      settings: {
        auto_tag: true,
        auto_tag_suffix: os + "-" + arch,
        dockerfile: "docker/Dockerfile." + os + "." + arch,
        repo: "appleboy/drone-discord",
        username: { "from_secret": "docker_username" },
        password: { "from_secret": "docker_password" },
      },
      when: {
        event: [ "push", "tag" ],
      },
    },
  ],
  depends_on: [
    "testing",
  ],
  trigger: {
    branch: [ "master" ],
  },
};

local PipelineNotifications = {
  kind: "pipeline",
  name: "notifications",
  platform: {
    os: "linux",
    arch: "amd64",
  },
  clone: {
    disable: true,
  },
  steps: [
    {
      name: "microbadger",
      image: "plugins/webhook:1",
      pull: "always",
      settings: {
        url: { "from_secret": "microbadger_url" },
      },
    },
  ],
  depends_on: [
    "linux-amd64",
    "linux-arm64",
    "linux-arm",
  ],
  trigger: {
    branch: [ "master" ],
    event: [ "push", "tag" ],
  },
};

[
  PipelineTesting,
  PipelineBuild("linux", "amd64"),
  PipelineBuild("linux", "arm64"),
  PipelineBuild("linux", "arm"),
  PipelineNotifications,
]

大家可以看到 local PipelineBuild 就是一個 func 函數,可以用來產生不同的環境代碼

  PipelineBuild("linux", "amd64"),
  PipelineBuild("linux", "arm64"),
  PipelineBuild("linux", "arm"),

完成後,直接在專案目錄下執行

$ drone jsonnet --stream

您會發現專案下的 .drone.yml 已經成功修正,未來只要將變動部分抽成變數,就可以產生不同專案的環境,開發者就不需要每次手動修改很多變動的地方。至於要不要把 .drone.jsonnet 放入專案內進行版本控制就看情境了。其實可以另外開一個新的 Repo 放置 .drone.jsonnet,未來新專案開案,就可以快速 clone 下來,並且產生新專案的 .drone.yml 設定檔。底下是 Drone 執行結果:

Drone Output


快速部署網站到 Heroku 雲平台

$
0
0

部署網站到 Heroku 平台

大家在寫開源專案時,一定需要一個免費的雲空間來放置網站,方便其他開發者在 GitHub 看到時,可以先點選 Demo 網站來試用,也許開發者可以使用 GitHub 提供的免費靜態網站,但是如果是跑 Golang 或是其他語言 Node.js 就不支援了,本篇來介紹 Heroku 雲平台,它提供了開發者免費的方案,您可以將 GitHub 儲存庫跟 Heroku 結合,快速的將程式碼部署上去,Heroku 會給開發者一個固定的 URL (含有 HTTPS 憑證),也可以動態的用自己買的網域。最重要的是 Heroku 提供了兩種更新方式,第一為 Git,只要開發者將程式碼 Push 到 Heroku 儲存庫,Heroku 就可以自動判斷開發者上傳的語言,並進行相對應的部署,另一種方式為 Docker 部署,只要在儲存庫內放上 Dockerfile,透過 Heroku CLI 指令就可以將 Docker 容器上傳到 Heroku Docker Registry,並且自動部署網站。底下我們來透過簡單的 Go 語言專案: Facebook Account Kit 來說明如何快速部署到 Heroku。

教學影片

歡迎訂閱我的 Youtube 頻道

使用 Git 部署

註冊網站後,可以直接在後台建立 App,此 App 名稱就是未來的網站 URL 前置名稱。進入 App 的後台,切換到 Deploy 的 Tab 可以看到 Heroku 提供了三種方式,本文只會講其中兩種,在開始之前請先安裝好 Heroku CLI 工具,底下所有操作都會以 CLI 介面為主。用 Git 來部署是最簡單的,開發者可以不用考慮任何情形,只要將程式碼部署到 Heroku 上面即可。在 Go 語言只要觸發 Push Event 系統會預設使用 go1.11.4 來編譯環境,產生的 Log 如下:

-----> Go app detected
-----> Fetching jq... done
 !!    
 !!    Go modules are an experimental feature of go1.11
 !!    Any issues building code that uses Go modules should be
 !!    reported via: https://github.com/heroku/heroku-buildpack-go/issues
 !!    
 !!    Additional documentation for using Go modules with this buildpack
 !!    can be found here: https://github.com/heroku/heroku-buildpack-go#go-module-specifics
 !!    
 !!    The go.mod file for this project does not specify a Go version
 !!    
 !!    Defaulting to go1.11.4
 !!    
 !!    For more details see: https://devcenter.heroku.com/articles/go-apps-with-modules#build-configuration
 !!    
-----> Installing go1.11.4
-----> Fetching go1.11.4.linux-amd64.tar.gz... done
 !!    Installing package '.' (default)
 !!    
 !!    To install a different package spec add a comment in the following form to your `go.mod` file:
 !!    // +heroku install ./cmd/...
 !!    
 !!    For more details see: https://devcenter.heroku.com/articles/go-apps-with-modules#build-configuration
 !!    
-----> Running: go install -v -tags heroku . 

系統第一步會偵測該專案使用什麼語言,就會產生相對應得環境,所以用 Git 方式非常簡單,開發者不需要額外設定就可以看到網站已經部署完畢,底下是 Git 基本操作,首先是登入 Heroku 平台。這邊會打開瀏覽器登入視窗。

$ heroku login

新增 Heroku 為另一個 Remote 節點

$ heroku git:clone -a heroku-demo-tw
$ cd heroku-demo-tw

簡單編輯程式碼,並且推到 Heroku Git 服務

$ git add .
$ git commit -am "make it better"
$ git push heroku master

使用 Docker 部署

Heroku 也提供免費的 Docker Registry 讓開發者可以寫 Dockerfile 來部署,底下是透過 Docker multiple stage 來編譯 Go 語言 App

FROM golang:1.11-alpine as build_base
RUN apk add bash ca-certificates git gcc g++ libc-dev
WORKDIR /app
# Force the go compiler to use modules
ENV GO111MODULE=on
# We want to populate the module cache based on the go.{mod,sum} files.
COPY go.mod .
COPY go.sum .
RUN go mod download

# This image builds the weavaite server
FROM build_base AS server_builder
# Here we copy the rest of the source code
COPY . .
ENV GOOS=linux
ENV GOARCH=amd64
RUN go build -o /facebook-account-kit -tags netgo -ldflags '-w -extldflags "-static"' .

### Put the binary onto base image
FROM plugins/base:linux-amd64
LABEL maintainer="Bo-Yi Wu <appleboy.tw@gmail.com>"
EXPOSE 8080
COPY --from=server_builder /app/templates /templates
COPY --from=server_builder /app/images /images
COPY --from=server_builder /facebook-account-kit /facebook-account-kit
CMD ["/facebook-account-kit"]

這裡面有一個小技巧,讓每次 Docker 編譯時可以 cache 住 golang 的 vendor,就是底下這兩行啦

# We want to populate the module cache based on the go.{mod,sum} files.
COPY go.mod .
COPY go.sum .
RUN go mod download

這時候只要我們沒有動過 go.* 相關檔案,每次編譯時系統就會自動幫忙 cache 相關 vendor 套件,加速網站部署,完成上述設定後,接著用 Heroku CLI 來完成最後步驟。首先在開發電腦上面必須安裝好 Docker 環境,可以透過底下指令來確認電腦是否有安裝好 Docker

$ docker ps

現在可以登入 Container Registry Now you can sign into Container Registry.

$ heroku container:login

上傳 Docker 映像檔到 Heroku,這邊會在 Local 直接編譯產生 Image

$ heroku container:push web

上面步驟只是上傳而已,並非部署上線,透過底下指令才能正確看到網站更新。

$ heroku container:release web

心得

上面所有的程式碼都可以在這邊找到,這邊我個人推薦使用 Docker 方式部署,原因很簡單,如果使用 Docker 部署,未來您不想使用 Heroku,就可以很輕易地轉換到 AWSGCP 等平台,畢竟外面的雲平台不可以提供 Git 服務並且自動部署。使用 Docker 形式也可以減少很多部署的工作,對於未來轉換平台來說是非常方便的。

在 docker-in-docker 環境中使用 cache-from 提升編譯速度

$
0
0

提升 docker build 時間

在現代 CI/CD 的環境流程中,使用 Docker In Docker 來編譯容器已經相當流行了,像是 GitLab CIDrone 都是全走 Docker 環境,然而有很多人建議盡量不要在 CI 環境使用 Docker In Docker,原因在於 CI 環境無法使用 Host Image 資料,導致每次要上傳 Image 到 Docker Hub 時都需要重新下載所有的 Docker Layer,造成每次跑一次流程都會重複花費不少時間,而這個問題在 v1.13 時被解決,現在只要在編譯過程指定一個或者是多個 Image 列表,先把 Layer 下載到 Docker 內,接著對照 Dockerfile 內只要有被 Cache 到就不會重新再執行,講得有點模糊,底下直接拿實際例子來看看。

教學影片

歡迎訂閱我的 Youtube 頻道: http://bit.ly/youtube-boy

更多實戰影片可以參考我的 Udemy 教學系列

使用 –cache-from 加速編譯

在 Docker v1.13 版本中新增了 --cache-from 功能讓開發者可以在編譯 Dockerfile 時,同時指定先下載特定的 Docker Image,透過先下載好的 Docker Layer 在跟 Dockerfile 內文比較,如果有重複的就不會在被執行,這樣可以省下蠻多編譯時間,底下拿個簡單例子做說明,假設我們有底下的 Dockerfile

FROM alpine:3.9
LABEL maintainer="maintainers@gitea.io"

EXPOSE 22 3000

RUN apk --no-cache add \
    bash \
    ca-certificates \
    curl \
    gettext \
    git \
    linux-pam \
    openssh \
    s6 \
    sqlite \
    su-exec \
    tzdata

RUN addgroup \
    -S -g 1000 \
    git && \
  adduser \
    -S -H -D \
    -h /data/git \
    -s /bin/bash \
    -u 1000 \
    -G git \
    git && \
  echo "git:$(dd if=/dev/urandom bs=24 count=1 status=none | base64)" | chpasswd

ENV USER git
ENV GITEA_CUSTOM /data/gitea

VOLUME ["/data"]

ENTRYPOINT ["/usr/bin/entrypoint"]
CMD ["/bin/s6-svscan", "/etc/s6"]

COPY docker /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
RUN ln -s /app/gitea/gitea /usr/local/bin/gitea

透過底下命令列可以編譯出 Image

$ docker build -t gitea/gitea .

而在命令列內可以看到花最多時間的是底下這個步驟

RUN apk --no-cache add \
    bash \
    ca-certificates \
    curl \
    gettext \
    git \
    linux-pam \
    openssh \
    s6 \
    sqlite \
    su-exec \
    tzdata

該如何透過 --cache-from 機制繞過此步驟加速 Docker 編譯時間,其實很簡單只要在網路上找到原本 image 就可以繞過此步驟,開發者總會知道原本的 Dockerfile 是用來編譯出哪一個 Image 名稱

$ docker build --cache-frome=gitea/gitea -t gitea/gitea .

從上圖可以知道時間最久的步驟已經被 cache 下來了,所以 cache-from 會事先把 Image 下載下來,接著就可以使用該 Image 內的 cache layer 享受簡短 build time 的好處。

在 Gitlab CI 使用 cache-from

在 Gitlab CI 如何使用,其實很簡單,請參考此範例

image: docker:latest
services:
  - docker:dind
stages:
  - build
  - test
  - release
variables:
  CONTAINER_IMAGE: registry.anuary.com/$CI_PROJECT_PATH
  DOCKER_DRIVER: overlay2
build:
  stage: build
  script:
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.anuary.com
    - docker pull $CONTAINER_IMAGE:latest
    - docker build --cache-from $CONTAINER_IMAGE:latest --build-arg NPM_TOKEN=${NPM_TOKEN} -t $CONTAINER_IMAGE:$CI_BUILD_REF -t $CONTAINER_IMAGE:latest .
    - docker push $CONTAINER_IMAGE:$CI_BUILD_REF
    - docker push $CONTAINER_IMAGE:latest

這時候你會問時間到底差了多久,在 Node.js 內如果沒有使用 cache,每次 CI 時間至少會多不少時間,取決於開發者安裝多少套件,我會建議如果是使用 multiple stage build 請務必使用 cache-from

在 Drone 如何使用 –cache-from

在 Drone 1.0 架構內,可以架設多台 Agent 服務加速 CI/CD 流程,但是如果想要跨機器的 storage 非常困難,所以有了 cache-from 後,就可以確保多台 agent 享有 docker cache layer 機制。底下來看看 plugins/docker 該如何設定。

- name: publish
  pull: always
  image: plugins/docker:linux-amd64
  settings:
    auto_tag: true
    auto_tag_suffix: linux-amd64
    cache_from: appleboy/drone-telegram
    daemon_off: false
    dockerfile: docker/Dockerfile.linux.amd64
    password:
      from_secret: docker_password
    repo: appleboy/drone-telegram
    username:
      from_secret: docker_username
  when:
    event:
      exclude:
      - pull_request

這邊拿公司的一個環境當作範例,在還沒使用 cache 前編譯時間為 2 分 30 秒,後來使用 cache-from 則變成 30 秒

結論

使用 --cache-from 需要額外多花下載 Image 檔案的時間,所以開發者需要評估下載 Image 時間跟直接在 Dockerfile 內直接執行的時間差,如果差很多就務必使用 --cache-from。不管是不是應用在 Docker In Docker 內,假如您需要改別人 Dockerfile,請務必先下載對應的 Docker Image 在執行端,這樣可以省去不少 docker build 時間,尤其是在 Dockerfile 內使用到 apt-get instllnpm install 這類型的命令。

開源專案 Gitea 支援 OAuth Provider

$
0
0

Gitea

很高興看到 Gitea 正式支援 OAuth Provider 了,此功能經歷了四個月終於正式合併進 master 分支,預計會在 1.8 版本釋出,由於此功能已經進 master,這樣我們就可以把原本 Drone 透過帳號密碼登入,改成使用 OAtuh 方式了,增加安全性。但是在使用之前,Drone 需要合併 drone/go-login@3drone/drone@2622。如果您會使用 Go 語言,不妨現在就可以來試試看了,透過 go build 來編譯原始碼。

影片教學

有興趣可以參考線上教學

安裝 Gitea

由於 Gitea 還沒轉到 Go module (已經有另外一個 PR 再處理 Vendor),所以請 clone 專案原始碼到 GOPATH 底下

$ git clone https://github.com/go-gitea/gitea.git \
  /go/src/code.gitea.io/gitea

接著切換到專案目錄,編譯出 SQLite 的 Binary

$ TAGS="sqlite sqlite_unlock_notify" make

編譯完成後,直接執行

$ ./gitea web
2019/03/09 12:26:03 [T] AppPath: /Users/appleboy/git/go/src/code.gitea.io/gitea/gitea
2019/03/09 12:26:03 [T] AppWorkPath: /Users/appleboy/git/go/src/code.gitea.io/gitea
2019/03/09 12:26:03 [T] Custom path: /Users/appleboy/git/go/src/code.gitea.io/gitea/custom
2019/03/09 12:26:03 [T] Log path: /Users/appleboy/git/go/src/code.gitea.io/gitea/log
2019/03/09 12:26:03 Serving [::]:3000 with pid 18284

打開瀏覽器登入後,進入右上角使用者設定,就可以建立新的 Application。

其中 Redirect URL 請填上 drone 的 URL http://localhost:8080/login

安裝 Drone

在上面有提到需要合併兩個 PR (drone@go-login#3drone@drone#2622) 才能使用此功能,等不及的朋友們就自己先 Fork 來使用吧。先假設已經合併完成。

$ cd $GOPAHT/drone
$ go build ./cmd/drone-server

然後建立 server.sh 將環境變數寫入

#!/bin/sh
export DRONE_GITEA_SERVER=http://localhost:3000
export DRONE_GITEA_CLIENT_ID=49de7c23-3bed-45a1-a78e-89c8ba4db07b
export DRONE_GITEA_CLIENT_SECRET=8GhG9XvPJEpaOroVocmJPAQArO5Zz7KMLQ5df0eG91c=
./drone-server

啟動 drone 服務,會看到一些 Info 訊息:

$ ./server.sh 
{"level":"info","msg":"main: internal scheduler enabled","time":"2019-03-09T12:39:21+08:00"}
{"level":"info","msg":"main: starting the local build runner","threads":2,"time":"2019-03-09T12:39:21+08:00"}
{"acme":false,"host":"localhost:8080","level":"info","msg":"starting the http server","port":":8080","proto":"http","time":"2019-03-09T12:39:21+08:00","url":"http://localhost:8080"}
{"interval":"30m0s","level":"info","msg":"starting the cron scheduler","time":"2019-03-09T12:39:21+08:00"}

打開瀏覽器輸入 http://localhost:8080 就可以看到跳轉到 OAuth 頁面

心得

現在 Gitea 已經支援 OAuth Provider,未來可以再接更多第三方服務,這樣就可以不用透過帳號密碼登入,避免讓第三方服務存下您的密碼。

10 分鐘內用 Traefik 架設 Drone 搭配 GitHub 服務

$
0
0

這標題也許有點誇張,但是如果實際操作一次,肯定可以在 10 分鐘內操作完成。本篇來教大家如何用 Traefik 當作前端 Proxy,後端搭配 Drone 服務接 GitHub,為什麼會用 Traefik,原因很簡單,你可以把 Traefik 角色想成是 Nginx,但是又比 Nginx 更簡單設定,另外一點就是,Traefik 自動整合了 Let’s Encrypt 服務,您就不用擔心憑證會過期的問題。假如機器只會有一個 Drone 當 Host 的話,其實也可以不使用 Traefik,因為 Drone 其實也是內建自動更新憑證的功能。如果您對 Traefik 有興趣,可以直接參考底下兩篇文章

教學影片

  • 00:37 架設 Traefik 服務 Listen 80 及 443 port
  • 02:42 用 Docker 架設 Drone 並且透過 Label 跟 Traefik 串接
  • 04:11 在 GitHub 申請 App Client ID 及 Secret

更多實戰影片可以參考我的 Udemy 教學系列

設定單一 Drone 服務

為什麼說是單一 Drone 服務呢,原因是在 Drone 1.0 開始支援在單一容器內就可以跑 server 跟 agent 同時啟動,降低大家入門門檻,本篇就是以單一容器來介紹,當然如果團隊比較大,建議還是把 server 跟 agent 拆開會比較適合。先建立 docker-compose.yml,相關代碼都可以在這邊找到

version: '2'

services:
  drone-server:
    image: drone/drone:1.0.0
    volumes:
      - ./:/data
      - /var/run/docker.sock:/var/run/docker.sock
    restart: always
    environment:
      - DRONE_SERVER_HOST=${DRONE_SERVER_HOST}
      - DRONE_SERVER_PROTO=${DRONE_SERVER_PROTO}
      - DRONE_TLS_AUTOCERT=false
      - DRONE_RUNNER_CAPACITY=3
      # GitHub Config
      - DRONE_GITHUB_SERVER=https://github.com
      - DRONE_GITHUB_CLIENT_ID=${DRONE_GITHUB_CLIENT_ID}
      - DRONE_GITHUB_CLIENT_SECRET=${DRONE_GITHUB_CLIENT_SECRET}
      - DRONE_LOGS_PRETTY=true
      - DRONE_LOGS_COLOR=true
    networks:
      - web
    logging:
      options:
        max-size: "100k"
        max-file: "3"
    labels:
      - "traefik.docker.network=web"
      - "traefik.enable=true"
      - "traefik.basic.frontend.rule=Host:${DRONE_SERVER_HOST}"
      - "traefik.basic.port=80"
      - "traefik.basic.protocol=http"

networks:
  web:
    external: true

接著建立 .env,並且寫入底下資料

DRONE_SERVER_HOST=
DRONE_SERVER_PROTO=
DRONE_GITHUB_CLIENT_ID=
DRONE_GITHUB_CLIENT_SECRET=

其中 proto 預設是跑 http,這邊不用動,traefik 會自動接上 container port,drone 預設跑在 80 port,這邊跟前一版的 drone 有些差異,請在 traefik.basic.port 設定 80 喔,接著跑 docker-compose up

$ docker-compose up
drone-server_1  | {
drone-server_1  |   "acme": false,
drone-server_1  |   "host": "drone.ggz.tw",
drone-server_1  |   "level": "info",
drone-server_1  |   "msg": "starting the http server",
drone-server_1  |   "port": ":80",
drone-server_1  |   "proto": "http",
drone-server_1  |   "time": "2019-03-21T17:13:32Z",
drone-server_1  |   "url": "http://drone.ggz.tw"
drone-server_1  | }

如果看到上面的訊息,代表已經架設完成。先假設各位已經都先安裝好 traefik,透過 docker label,traefik 會自動將流量 proxy 到對應的 container。

心得

這是我玩過最簡單的 CI/CD 開源專案,設定相當容易,作者花了很多心思在這上面。另外我會在四月北上參加『CI / CD 大亂鬥』擔任 Drone 的代表,希望可以在現場多認識一些朋友,如果對 Drone 有任何疑問,隨時歡迎找我,或直接到現場交流。

用五分鐘了解什麼是 unbuffered vs buffered channel

$
0
0

golang logo

本篇要跟大家聊聊在 Go 語言內什麼是『unbuffered vs buffered channel』,在初學 Go 語言時,最大的挑戰就是了解 Channel 的使用時機及差異,而 Channel 又分為兩種,一種是 buffered channel,另一種是 unbuffered channel,底下我來用幾個簡單的例子帶大家了解這兩種 channel 的差異,讓初學者可以很快的了解 channel 使用方式。

教學影片

直接看影片比較快,五分鐘左右就可以初學了解 channel

  • 00:22 unbuffered channel
  • 02:32 buffered channel

更多實戰影片可以參考我的 Udemy 教學系列

Unbuffered Channel

在 Go 語言內使用 goroutine 是很常見的,但是我們該如何透過 Channel 來解決同步問題,請看底下例子

func main() {
    go func() {
        fmt.Println("GO GO GO")
    }()
    time.Sleep(1 * time.Second)
}

上面的例子在執行完 main 函數,就會直接跳出,所以後面設定了等待一秒可以解決此問題,但是一班開發模式不會加上 Timeout,這邊該如何透過 Unbuffered Channel 方式來達到一樣的效果。

func main() {
    c := make(chan bool)
    go func() {
        fmt.Println("GO GO GO")
        c <- true
    }()
    <-c
}

可以看到上面我用了 make(chan bool) 來建立一個 channel,當在 main 函數最後用 <-c 代表需要等待讀出一個 channel 值,main 函數才會結束,這時候就達到了跟用 Sleep 一樣的效果,接著將程式碼改成如下:

func main() {
    c := make(chan bool)
    go func() {
        fmt.Println("GO GO GO")
        <-c
    }()
    c <- true
}

你會發現得到同樣的結果,為什麼呢?因為 unbufferd channel 的用途就是,今天在程式碼內丟了一個值進去 channel,這時候 main 函式就需要等到一個 channel 值被讀出來才會結束,所以不管你在 goroutine 內讀或寫,main 都需要等到一個寫一個讀完成後才會結束程式。這就是用 unbuffered channel 來達到同步的效果,也就是保證讀寫都需要執行完畢才可以結束主程式。

buffered Channel

那 buffered Channel 呢,差異在於宣告方式,請看底下例子

func main() {
    c := make(chan bool, 1)
    go func() {
        fmt.Println("GO GO GO")
        <-c
    }()
    c <- true
}

可以很清楚知道 buffered channel 是透過 make(chan bool, 1) 後面有帶容量值,開發者可以一次宣告此 channel 可以容納幾個值,大家可以執行看看上述程式碼,會發現完全沒有輸出東西,原因是什麼,buffered channel 就是只要容量有多少,你都可以一直塞值進去,但是不用讀出來沒關係,所以當丟了 c <- true 進去後,主程式不會等到讀出來才結束,造成使用者沒有看到 fmt.Println("GO GO GO"),這就是最大的差異。

結論

大家記住 unbuffered channel 就是代表在主程式內,需要等到讀或寫都完成,main 才可以完整結束,而 buffered channel 相反,你可以一直丟資料進去 Channel 內,但是不需要讀出來,所以 main 才提前結束。如果您想要用 channel 做到同步或異步的效果,這邊就需要注意了。希望本邊有幫助到大家初學 Channel。之後會有更多深入的文章來講解 Channel,如果要看更多 Go 影片可以參考這邊

Viewing all 325 articles
Browse latest View live