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

開發者另類的自架 Git 服務選擇: Gitea

$
0
0
gitea 現在 Git 服務最有名的就是 Github,如果是開放原始碼,我很推薦 Github。如果是想要放大量私有專案或企業內及個人使用,想必大家會推薦 Gitlab,在這裡就不多介紹 Gitlab 了,可以從 Google 找到許多相關資料,本篇會介紹另類的 Git 自架服務選擇 Gitea,在介紹之前可以先參考我在 2014 年寫了一篇用 Go 語言開發的 Git 服務叫做 Gogs

緣起

Gitea 是 2016 下半年由 @bkcsoft, @lunny@tboerger其他開發者共同發起,從原本的 Gogs 專案在分支出來,在剛開始起來的時候,很多開發者一直詢問為什麼會有 Gitea,而不是持續開發 Gogs,於是 Gitea 官方寫了一篇文章關於 Gitea 誕生,裡面詳細介紹為什麼會有 Gitea,有興趣的可以去看看,最主要的原因就是為了讓專案可以持續發展,而不是受限於個人因素,大家都知道 Open Source 到最後最難持續的就是找到更多人來維護專案,所以 Gitea 為了避免 Issue 或 Pull Request 太久沒人處理,我們訂了一個機制,就是 PR 只要通過兩位 Reviewer 留言 LGTM 就可以直接 Merged,這也讓更多開發者願意貢獻到 Gitea。從去年 12 月到現在 2017 年 1 月,已經 Release 了兩個版本。

安裝

為了能讓 Gogs 用戶可以無痛轉換到 Gitea,我們寫了一篇升級教學,除此之外,也提供了各種安裝方式: Docker 安裝下載執行檔用套件Windows 安裝自行編譯 .. 等各種方式。最簡單的方式就是透過下載執行檔,只要一個指令就可以看到歡迎畫面了
./gitea web
安裝頁面   Gitea  Git with a cup of tea 將來如果要升級 gitea,更是容易,你不用停掉 gitea 服務,只要下載新版執行檔,覆蓋掉原有的執行檔,接著下:
kill -USR2 gitea_pid
就可以完成升級,原本的服務也不會因此而中斷。這要感謝 Facebook 開發的 grace 套件,這套件並不支援 Windows,所以上面的升級方式不適用在 Windows 平台,不過我相信很少人會把 Git 服務架在 Windows 系統。

總結

在這邊先跟大家聊一下為什麼要選 Gitea:
  1. Gitea 是用 Golang 所撰寫。
  2. 使用介面跟 Github 很類似,如果你已經很習慣 Github,那轉換到 Gitea 一定不會很陌生。
  3. 安裝及升級比其他服務來得容易 (Gitlab, Bitbucket)
  4. 如果家中有 Raspberry Pi 硬體,你可以輕易地將 Gitea 放在上面執行
  5. 跨平台: Gitea 可以運作在任何 Go 能夠編譯的平台: Windows, macOS, Linux, ARM 等等
現在官方有支援繁體中文,歡迎大家加入翻譯行列。

從商業利益看 Go 程式語言

$
0
0
Go-brown-side.sh 從 2016 年開始寫 Go 程式語言,在這一年我向很多朋友介紹了 Go 語言,很多人就不經問到為什麼我這麼喜歡 Go 語言,在公司內同事或主管更會問,為什麼要從 Node.js 或其他語言轉換到 Go,Go 語言有什麼地方可以帶給公司更大的利益,否則為什麼要多花時間跟人力去嘗試 Go 語言。如果團隊要建置一個商業 Web 服務,那我覺得底下的優點,是讓您選擇 Go 語言的最主要原因。

Go 優勢

  • 快速又簡單部署
  • 支援跨平台編譯
  • 保護原始程式碼
  • 善用多核心處理
底下會針對上述提到的優點進行詳細說明。

1. 快速部署

傳統部署

大家都知道 Golang 可以將專案程式碼編譯成一個二進制檔案,直接部署此檔案會比上傳上百的或上千個檔案還要來得容易。傳統上傳多個檔案是非常慢的,這就是為什麼我們在部署之前會將所有檔案透過 zip 或 tar 的方式打包成一個檔案再部署到機器上,除此之外,上傳後,伺服器端要另外解壓縮或者做其他事情,想想看要寫多少 Shell Script 才能完成此事情,而這些事情都是要靠 DevOps 工程師去撰寫,以及花時間去調整 CI 伺服器 (像是 Jenkins)。

伺服器設定

當然還沒完,檔案上傳完後,每一台伺服器都需要準備相關軟體及設定,這時候可以透過 DockerPuppetChef 完成這些事情,這些都是要花很大量的時間去處理,另外還要處理相對應安全性問題,像是 PHPPythonRuby 甚至 Node.js 版本的升級,太多層面需要考量。

用 Go 語言

在 Go 語言,你只需要將程式碼編譯出二進制執行檔,就可以直接丟到多台伺服器,直接啟動,並不需要準備各種環境或軟體設定,省下來的時間就是大量的金錢。如果是用 Go 語言,我相信團隊內的 DevOps 工程師會很感謝你。假如團隊還沒將 DevOps 流程納入公司的商業考量,那您就落伍了。一位好的 DevOps 工程師可以幫團隊省下很多資金。

2. 不需要 Web 伺服器

這點其實就跟 Node.js 很像,你不需要 Apache 或 Tomcat 或 Nginx 等相關服務,Go 的執行檔就可以處理 Http 連線,這意味著不會有伺服器設定,也不需要維護成本,更不用考慮伺服器安全性問題。當然你執意要使用 load balance 像是 NginxHAProxy 這時候就會變得比較難除錯,也會多花不少時間再調整設定。

3. 保護程式碼

這點非常重要,如果你是賣軟體或者是雲端的公司,這時候客戶希望你將整套雲端放置在客戶公司內部,讓他們測試使用,這時候如果你是使用 Node.js, Ruby 或 PHP 的話,你只能乖乖的將整套原始碼放置在對方公司,並且心驚膽跳的怕對方有意去複製整套程式碼,這時候你敢跟老闆講說,我們需要將程式碼放到對方公司嗎?但是如果是在 Go,我們只需要給客戶一個檔案,請他們直接執行,就可以將服務跑起來,如果想要 decompile 此 binary,答案是 NO。

4. 多核心處理

Go 是個多核心處理的程式語言,這對於伺服器非常重要,許多 web 多語言都是跑 single thread,所以會閒置許多系統效能,現在的伺服器幾乎都是多核 CPU 了,像在 Node.js 你就必須在一台機器跑多個 Node 服務,才可以有效率地發揮效能。在 Go 1.5 版本後 (含 1.5) 已經將 GOMAXPROCS 預設值為系統可用的多核數,所以只要啟動一個執行檔,就可以將系統效能發揮到極限。

5. 跨平台編譯

Go 語言可以將程式碼編譯成 WindowsMacOSLinux 的執行檔,方便攜帶或測試,想看看業務到外面跟客戶 Demo,還要連到 Internet 才可以 Demo 給客戶看,如果網路環境不好或者是沒網路該怎麼辦呢?這時候你只需要攜帶一個執行檔在身上就可以解決您的問題。在團隊內部,每次 QA 都要等工程師部署到內部機器才可以開始測試,這時只要給他們執行檔,就不需要等待工程師部署才可以測試,省下多少時間,這些寶貴的時間都是金錢啊。

結論

其實你會發現上面提到的所有優勢都是省下時間金錢,時間在公司是非常重要及寶貴,大家可以想看看,省下的時間是不是可以讓團隊多做一些事情,讓公司更進步更有競爭力呢?

Caddy 搭配 Drone 伺服器設定

$
0
0
The_Caddy_web_server_logo.svg

緣由

Caddy 是一套 HTTP/2 伺服器,你可以想成跟 Nginx 是同一種角色,但是 Caddy 有一點不一樣的地方就是自動支援 HTTPS 設定,也就是 Caddy 幫網站自動申請 Letsencrypt 憑證,開發者不需要擔心憑證會過期,Caddy 會定期幫忙更換。Drone 則是一套以 Docker 為基礎的 Continuous Integration 平台。就在上個月 Caddy 發佈了 0.9.5 版本,更新過後,發現 Drone 的 WebSocket 連線會斷線又連線,底下來看看 Caddy 更動了什麼造成 WebSocket 連線失效。

Caddy 預設將 HTTP Timeouts 啟動

0.9.5 版本以前,Caddy 預設是沒有將 Timeouts 模組載入,所以只要使用底下設定就可以成功串起 Drone 服務
example.com {
  proxy / localhost:8000 {
    websocket
    transparent
  }
}
Drone 預設跑 8000 Port,另外在 Agent 跑 wss 連線
export DRONE_SERVER="wss://example.com/ws/broker"
Caddy 在 0.9.5 版本啟動 Timeouts 模組,也就是沒有支援 long connection,預設值如下
  • read: 10s (time spent reading request headers and body)
  • header: 10s (time spent reading just headers; not used until Go 1.8 is released)
  • write: 20s (starts at reading request body, ends when finished writing response body)
  • idle: 2m (time to hold connection between requests; not used until Go 1.8 is released)
也就是每 10 秒就會自動斷線在連線,解決方式也不難,就是把 Timeouts 模組關閉
example.com {
  # please comment the timeouts configures
  # if caddy server version under 0.9.5
  timeouts none
  proxy / localhost:8000 {
    websocket
    transparent
  }
}
這樣就可以解決 Drone 搭配 Caddy 0.9.5 版本的問題,請注意 timeouts 只支援 0.9.5 以上版本,非此版本就會出現底下錯誤
Parse error: unknown property ‘timeouts’

結論

大家可以盡快更新 Caddy,因為在 0.9.5 版本強化了 Proxy 效能。更多更新可以直接參考 Release Notes

用 Golang 寫 Command line 工具

$
0
0
Go-brown-side.sh 如果你要寫 Command line 工具,又想在各平台 (像是 MacOS, Windows 或 Linux) 上執行,這時候 Golang 就是您最好的選擇。在 Reddit 讀到一篇 Command line 工具比較介紹,這篇最主要講到兩個 CLI 工具,一個是 urfave/cli,另一個是 spf13/cobra,這兩個工具其實都非常好用,後者是去年加入 Google Golang 團隊spf13 所開發,該作者加入 Google 後呢,非常的忙,但是強者他同事有幫忙繼續維護 cobra 專案,兩個 CLI 工具各自都有有大型專案使用 urfave/cli 有 docker/libcompose, docker/machine, Drone, Gitea, Gogs 等,而後者 spf13/cobra 則有 docker, docker/distribution, etcd 等。本篇筆者會介紹 urfave/cli 該如何使用?

用 Golang 內建 flag 套件

其實 Golang 本身就有支援 Command line 功能,只要 import flag 就可以直接使用了
package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {
    var showVersion bool

    flag.BoolVar(&showVersion, "version", false, "Print version information.")
    flag.BoolVar(&showVersion, "v", false, "Print version information.")
    flag.Parse()

    // Show version and exit
    if showVersion {
        fmt.Println("Version 1.0.0")
        os.Exit(0)
    }
}
存檔成 main.go,執行 go build -o main 就可以產生 main 執行檔,最後可以直接下 ./main -v 畫面就會顯示 Version 1.0.0。但是如果 flag 非常多,寫起來就會相當長,也不支援讀取 Environment 環境變數,這時候我們可以透過 urfave/cli 來簡化此流程。上面的範例可以在底下連結找到

用 Golang flag 寫 Command Line

使用 urfave/cli 套件

底下是一個簡單範例,可以從 command line 讀取使用者帳號密碼
package main

import (
    "fmt"
    "os"

    "github.com/urfave/cli"
)

type (
    // Config information.
    Config struct {
        username string
        password string
    }
)

var config Config

func main() {
    app := cli.NewApp()
    app.Name = "Example"
    app.Usage = "Example"
    app.Action = run
    app.Flags = []cli.Flag{
        cli.StringFlag{
            Name:  "username,u",
            Usage: "user account",
        },
        cli.StringFlag{
            Name:  "password,p",
            Usage: "user password",
        },
    }

    app.Run(os.Args)
}

func run(c *cli.Context) error {
    config = Config{
        username: c.String("username"),
        password: c.String("password"),
    }

    return exec()
}

func exec() error {
    fmt.Println("username:", config.username)
    fmt.Println("password:", config.password)

    return nil
}
從上面例子可以發現從 Name 就可以定義 Flag 名稱,用逗號分格,在命令列就可以使用 -u--username,也會自動幫忙產生完整的 help 畫面 Screen Shot 2017-02-16 at 2.27.11 PM

支援環境變數

在 Golang 可以快速的將執行檔打包成 Docker Image
FROM centurylink/ca-certs

ADD main /

ENTRYPOINT ["/main"]
在 Dockerfile 內使用參數可以透過 CMD 會變成底下
FROM centurylink/ca-certs

ADD main /

ENTRYPOINT ["/main"]
CMD ["-u", "appleboy"]
這樣非常麻煩,這時候就要讓 CLI 也支援環境變數,將 cli.StringFlag 改成如下
    app.Flags = []cli.Flag{
        cli.StringFlag{
            Name:   "username,u",
            Usage:  "user account",
+           EnvVar: "DOCKER_USERNAME",
        },
        cli.StringFlag{
            Name:   "password,p",
            Usage:  "user password",
+           EnvVar: "DOCKER_PASSWORD",
        },
    }
直接在命令列執行 DOCKER_USERNAME=appleboy ./main,則就會抓到 DOCKER_USERNAME 環境變數,在 Docker 指令就可以補上 -e 參數來實現變數傳遞:
$ docker run -e DOCKER_USERNAME=appleboy appleboy/cli

結論

把 Golang 執行檔包進去 Docker Image,就可以再任意環境內執行,如果你不想使用 Docker Image 也沒關係,Golang 支援跨平台編譯,底下是支援 Windows, Linux, MacOS 編譯參數
    GOOS=linux   GOARCH=amd64 CGO_ENABLED=0 go build -o bin/main-linux
    GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o bin/main.exe
    GOOS=darwin  GOARCH=amd64 CGO_ENABLED=0 go build -o bin/main-darwin
自從學了 Golang,讓用 Windows 工作的同事,也可以享用 Golang 的好處。上述範例檔案可以參考底下連結

用 urfave/cli 寫 Command Line

Go 語言的錯誤訊息處理

$
0
0
Go-brown-side.sh 每個語言對於錯誤訊息的處理方式都不同,在學習每個語言時,都要先學會如何在程式內處理錯誤訊息 (Error Handler),而在 Go 語言的錯誤處理是非常簡單,本篇會用簡單的範例教大家 Go 如何處理錯誤訊息。

Go 輸出錯誤訊息

在 Go 語言內有兩種方式讓函示 (function) 可以回傳錯誤訊息,一種是透過 errors 套件或 fmt 套件,先看看 errors 套件使用方式:
package main

import (
    "errors"
    "fmt"
)

func isEnable(enable bool) (bool, error) {
    if enable {
        return false, errors.New("You can't enable this setting")
    }

    return true, nil
}

func main() {
    if _, err := isEnable(true); err != nil {
        fmt.Println(err.Error())
    }
}
請先引入 errors 套件,接著透過 errors.New("message here"),就可以實現 error 錯誤訊息。接著我們打開 errors package 原始碼來看看
package errors

// New returns an error that formats as the given text.
func New(text string) error {
    return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}
可以發現 errors 套件內提供了 New 函示,讓開發者可以直接建立 error 物件,並且實現了 error interface。在 Go 語言有定義 error interface 為:
type error interface {
    Error() string
}
只要任何 stuct 有實作 Error() 接口,就可以變成 error 物件。這在下面的自訂錯誤訊息會在提到。除了上面使用 errors 套件外,還可以使用 fmt 套件,將上述程式碼改成:
func isEnable(enable bool) (bool, error) {
    if enable {
        return false, fmt.Errorf("You can't enable this setting")
    }

    return true, nil
}
這樣也可以成功輸出錯誤訊息,請深入看 fmt.Errorf
// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
func Errorf(format string, a ...interface{}) error {
    return errors.New(Sprintf(format, a...))
}
你可以發現在 fmt 套件內,引用了 errors 套件,所以基本上本質是一樣的。

Go 錯誤訊息測試

在 Go 語言如何測試錯誤訊息,直接引用 testing 套件
package error

import "testing"

func TestIsMyError(t *testing.T) {
    ok, err := isEnable(true)

    if ok {
        t.Fatal("should be false")
    }

    if err.Error() != "You can't enable this setting" {
        t.Fatal("message error")
    }
}
另外 Go 語言最常用的測試套件 Testify,可以改寫如下:
package error

import (
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestIsEnable(t *testing.T) {
    ok, err := isEnable(true)
    assert.False(t, ok)
    assert.NotNil(t, err)
    assert.Equal(t, "You can't enable this setting", err.Error())
}

Go 自訂錯誤訊息

從上面的例子可以看到,錯誤訊息都是固定的,如果我們要動態改動錯誤訊息,就必須帶變數進去。底下我們來看看如何實現自訂錯誤訊息:
package main

import (
    "fmt"
)

// MyError is an error implementation that includes a time and message.
type MyError struct {
    Title   string
    Message string
}

func (e MyError) Error() string {
    return fmt.Sprintf("%v: %v", e.Title, e.Message)
}

func main() {
    err := MyError{"Error Title 1", "Error Message 1"}
    fmt.Println(err)

    err = MyError{
        Title:   "Error Title 2",
        Message: "Error Message 2",
    }
    fmt.Println(err)
}
也可以把錯誤訊息包成 Package 方式
package error

import (
    "fmt"
)

// MyError is an error implementation that includes a time and message.
type MyError struct {
    Title   string
    Message string
}

func (e MyError) Error() string {
    return fmt.Sprintf("%v: %v", e.Title, e.Message)
}
main.go 就可以直接引用 error 套件
package main

import (
    "fmt"

    my "github.com/go-training/training/example04/error"
)

func main() {
    err := my.MyError{"Error Title 1", "Error Message 1"}
    fmt.Println(err)

    err = my.MyError{
        Title:   "Error Title 2",
        Message: "Error Message 2",
    }
    fmt.Println(err)
}
如何測試錯誤訊息是我們自己所定義的呢?請在 error 套件內加入底下測試函示
func IsMyError(err error) bool {
    _, ok := err.(MyError)
    return ok
}
由於我們實作了 error 接口,只要是 Interface 就可以透過 Type assertion 來判斷此錯誤訊息是否為 MyError
package error

import "testing"

func TestIsMyError(t *testing.T) {
    err := MyError{"title", "message"}

    ok := IsMyError(err)

    if !ok {
        t.Fatal("error is not MyError")
    }

    if err.Error() != "title: message" {
        t.Fatal("message error")
    }
}
這樣在專案裡就可以實現多個錯誤訊息,寫測試時就可以直接判斷錯誤訊息為哪一種自訂格式。

結論

在 Go 裡面寫錯誤訊息真的很方便又很容易,動態訊息請自定,反之,固定訊息請直接宣告 const 就可以了。在寫套件給大家使用時,大部份都是使用固定訊息,如果是大型專案牽扯到資料庫時,通常會用動態自訂錯誤訊息比較多。

上述程式碼請參考這裡

Go 語言官方推出的 dep 使用心得

$
0
0
Go-brown-side.sh Go 語言團隊在去年開始開發 Dependency Management Tool 稱作 dep,並且預計明年 2018 推出 1.10 Go 版本時內建,詳細可以參考官方的 roadmap,強者我朋友寫了一篇使用教學,有興趣的朋友可以參考看看,但是本篇會講幾點我目前不打算用 dep 的原因。

dep ensure 預設是抓 $GOPATH 的路徑

dep init 或 dep ensure 預設會先去掃 $GOPATH 底下是否存在您所需要的 Package 原始碼,如果有,預設就會去抓到 vendor 目錄,但是這點對開發者很困擾,在 $GOPATH 底下的專案都不是最新的,有時候自己還會去修改自己開發的 Package,這樣造成開發者還要下一次指令去更新 vendor 套件 (請加上 -update 參數)。

不支援抓 sub package.

其他 Dependency tool 幾乎都有支援抓 sub package,像是 govendorglide
govendor fetch github.com/joho/godotenv/autoload
上面指令只會抓 godotenv 內的 autoload package 原始碼,跟 autoload 無關的一律不抓,但是在 dep 只能能抓 godotenv 全部資料,不支援底下寫法,會直接報錯
dep ensure github.com/joho/godotenv/autoload

dep 預設抓不相關的檔案到 vendor

dep 預設會抓非 .go 或者是不相關的檔案到 vendor 目錄,像是 .travis.yml 另外也把 _test.go 也一起抓進來,造成整個 venodr 有點肥。相對像是 govendor 只會抓專案會用到的 *.goLicense 檔案,所以在 vendor 目錄底下相對看起來蠻清楚的。

結論

綜合上面三點,我個人不推薦現在使用 dep,非常不穩定,未來官方還會修正 .json 或 .lock 檔案格式,現在要決定的話,我會等到年底或者是明年初再開始使用,大部份的 Open Source 專案還是都是使用 govendor 或 glide …等相對穩定的工具。我個人推薦 govendor 啦。

Debian/Ubuntu 的 update-rc.d 使用教學

$
0
0
Debian update-rc.d 是在 DebianUbuntu 內用來管理 /etc/init.d 目錄內的 scripts 工具。不管是 Nginx 或 Mysql 等相關服務,都可以在 /etc/init.d 目錄內找到相對應的 script 檔案,隨便打開一個 script 檔案就可以看到標頭有固定的格式寫法:
### BEGIN INIT INFO
# Provides:          gorush
# Required-Start:    $syslog $network
# Required-Stop:     $syslog $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the gorush web server
# Description:       starts gorush using start-stop-daemon
### END INIT INFO
從上面格式可以看到預設啟動模式可以在 2 3 4 5 其餘的 0 1 6 則是關閉,這邊基本上可以不用動它,詳細的寫法可以直接參考 /etc/init.d/skeleton 檔案,或者是直接複製修改即可。由於筆者都在寫 Go 語言,包成二進制執行檔後,就必須靠 update-rc.d 產生相對應的 scripts。
  • 0 關機模式
  • 1 單機使用
  • 6 重新開機

使用方式

/etc/init.d 目錄下寫好 script 後,可以用 update-rc.d 自動在 /etc/rcX 產生 link 檔案,請直接使用底下指令
$ update-rc.d gorush default 20
如果執行上述指令遇到底下錯誤:
update-rc.d: warning: start runlevel arguments (none) do not match gorush Default-Start values (2 3 4 5) update-rc.d: warning: stop runlevel arguments (none) do not match gorush Default-Stop values (0 1 6)
請直接將指令改成
$ update-rc.d gorush start 20 2 3 4 5 . stop 80 0 1 6 .
如果您的服務必須先將 Mysql 啟動,有兩種方式解決這問題,第一種是透過 update-rc.d 修改起動順序:
$ update-rc.d mysqld defaults 80 20
$ update-rc.d gorush defaults 90 10
上面就是代表啟動時,先啟動 mysqld 後啟動 gorush,關機時,先停止 gorush 後停止 mysqld。個人不推薦使用這方法,另一個方式就是調整 script 標頭內容
- # Required-Start:    $syslog $network
+ # Required-Start:    $mysqld $syslog $network
這樣就可以確保執行 gorush 前,MySQL 服務已經先啟動了。

在 Go 語言用一行程式碼自動化安裝且更新 Let’s Encrypt 憑證

$
0
0
Go-brown-side.sh 在去年寫了一篇『申請 Let’s Encrypt 免費憑證讓網站支援 HTTP2』教學,如果您是用 Nginx,就可以參考該篇教學讓您的伺服器支援 HTTPS,而 Google Security Blog 也宣布在 56 版本以後將會提示 non-secure 網站,讓使用者可以選擇性瀏覽網站。Let’s Encrypt 官方也公布去年 2016 發了多少張憑證,相當驚人,想必大家對 HTTPS 已經有相當程度的瞭解。底下這張圖說明 2016 年 Let’s Encrypt 發憑證總量的狀況 Screen Shot 2017-04-07 at 9.52.40 AM 此篇會介紹在 Go 語言如何跟 Let’s Encrypt 串接,底下有兩種方式。

使用 Caddy 伺服器

大家可以把 Caddy 想成跟 Nginx 同等關係,不同的是 Caddy 是一套完全用 Go 語言打造的伺服器,這邊就會介紹 Caddy 怎麼設定 HTTPS 憑證。先假設 domain 為 example.com,底下就是讓此 domain 自動掛上憑證的 Caddyfile 設定檔
example.com {
  proxy / localhost:3000
}
上面設定會自動將 http 轉換成 https,也就是在瀏覽器鍵入 http://example.com Caddy 會自動換成 https://example.com。如果是 Nginx 呢?看看底下設定
server {
  listen 0.0.0.0:80;
  server_name example.com;

  location /.well-known/acme-challenge/ {
    alias /var/www/dehydrated/;
  }

  return 301 https://example.com$request_uri;
}

server {
  # listen 80 deferred; # for Linux
  # listen 80 accept_filter=httpready; # for FreeBSD
  listen 0.0.0.0:443 ssl http2;

  ssl_certificate /etc/dehydrated/certs/example.com/fullchain.pem;
  ssl_certificate_key /etc/dehydrated/certs/example.com/privkey.pem;

  # The host name to respond to
  server_name codeigniter.org.tw;
}
很明顯可以看出,用 Caddy 大勝 Nginx 設定檔簡易程度。另外 Let’s Encrypt 憑證會在三個月後過期,如果是使用 Caddy,可以不用擔心過期問題,Caddy 會自動在三個月內幫忙更新憑證有效日期,如果是 Nginx,請寫 Script 並且放到 Crontab 內。結論就是 Caddy 自動幫忙處理申請+更新憑證,而 Nginx 都必須手動打造。

使用 Go 語言 autocert package

相信很多 Go 開發者不希望前面有 Proxy Server 像是 Caddy 或 Nginx,而是希望 Go Binary 可以直接 Listen 443 port,這樣好處就是 Performance 會是最好,缺點就是一台機器只能使用一個 application。而 Go 語言要實現整合 Let’s Encrypt 相當容易,只需要引入 autocert 套件,底下是範例程式碼: (使用 Gin framework)

程式碼範例

package main

import (
    "crypto/tls"
    "net/http"

    "github.com/gin-gonic/gin"
    "golang.org/x/crypto/acme/autocert"
)

func main() {
    r := gin.Default()

    // Ping handler
    r.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })

    m := autocert.Manager{
        Prompt:     autocert.AcceptTOS,
        HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
        Cache:      autocert.DirCache("/var/www/.cache"),
    }

    s := &http.Server{
        Addr:      ":https",
        TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
        Handler:   r,
    }
    s.ListenAndServeTLS("", "")
}
程式碼內有兩個地方需要注意,一個是 HostWhitelist 這是綁定特定 Domain,請務必填寫,當然你也可以填空,但是這樣任意 Domain 指向你的機器,就可以直接對 Let’s Encrypt 請求憑證,所以請務必填上自己的 Domain,另一個是 DirCache 這是憑證存放的目錄,你可以任意指定到其他目錄,第一次請求會比較久,原因是憑證還沒下來。上面範例你會發現,哪是一行,看起來就是好幾行才完成此功能,在不久之前(本週) @bradfitz (Go 語言 HTTP 核心開發者) 開發了 Listener 函示 (相關 Commit),讓開發者可以用一行取代上面冗長的程式碼:

程式碼範例

package main

import (
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    "golang.org/x/crypto/acme/autocert"
)

func main() {
    r := gin.Default()

    // Ping handler
    r.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })

    log.Fatal(http.Serve(autocert.NewListener("example1.com", "example2.com"), r))
}
現在只要填入您的 Domain 就可以綁定憑證及自動更新 (不必擔心三個月會過期),這邊你會問,那憑證是放在哪裡呢?
  • MacOS 放在 /Users/xxxx/Library/Caches/golang-autocert
  • Windows ${HOMEDRIVE}/${HOMEPATH} 加上依序先找 APPDATA, CSIDL_APPDATA, TEMP, TMP 全域變數的目錄
  • Linux 放在家目錄內 .cache/golang-autocert 目錄
如果是搭配 Docker,你可以指定 XDG_CACHE_HOME 變數來轉換您想要的目錄。請在 Dockerfile 內放入
ENV XDG_CACHE_HOME /var/lib/.cache
這樣 docker 會把憑證放在 /var/lib/.cache/golang-autocert 內,使用者不用管憑證放哪裡,因為就算消失了,下次重新啟動,自然會在產生一次。最後要講的是,如何用 Go 語言實現 http 轉到 https 呢?非常簡單,請參考底下程式碼

程式碼範例

var g errgroup.Group

g.Go(func() error {
  return http.ListenAndServe(":http", http.RedirectHandler("https://example.com", 303))
})
g.Go(func() error {
  return http.Serve(autocert.NewListener("example.com"), handler)
})

if err := g.Wait(); err != nil {
  log.Fatal(err)
}

總結

不管是用 Caddy 或者是 Go 語言 autocert 套件都是非常簡單,如果寫 Open Source 專案,基本上就是用 Go 語言內建的 autocert 來實現 Let’s Encrypt 串接,方便大家下載 Binary 直接 Listen 443 Port。不熟 Go 語言,就試試看 Caddy 伺服器吧。相信跑過一陣子你會發現 Caddy 的好處。

五大理由從 Python 轉到 Go 語言

$
0
0
Go-brown-side.sh 在網路上看到這篇『5 Reasons Why We switched from Python To Go』,先發到自己 Facebook 牆上,引發討論,乾脆整理一篇 Blog 來寫自己的感想,底下五大理由讓該篇作者從 Python 轉到 Go 語言。我會針對前四點來寫心得
  1. 編譯二進制檔案 (加速部署及跨平台)
  2. 編譯自動檢查 Static 型態 (你不會把 string 欄位帶入 Integer)
  3. 效能 (Go 並發跟 Python thread 比起來節省許多資源)
  4. 不需要 web framework (Go 內建大多數 Library 像是 HTTP, JSON, HTML templating)
  5. 好用的 IDE (內文提到 Webstorm, PHPStorm) 我推薦用 VSCode
除了第五點外,其他四點個人覺得都是工程師的痛點。

1. It Compiles Into Single Binary

由於現在 Web Application 技術越來越先進,所以造成 CI/CD 流程相對複雜,所以每次只要 commit code,部署 + 測試時間相當久,在 Go 語言可以把前端 Source Code 整個包進去 Go Binary,所以 Production 機器根本不需要安裝任何 Package 就可以進行部署,這省下的時間對於大團隊而言是很可觀的。在 Go 語言只需要一個指令,就可以直接 build 出 binary file (不管是 ARM, Linux, MacOS, Windows) 32 bit or 64 bit
$ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o hello package
$ GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -o hello package
$ GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o hello package
$ GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o hello.exe package

2. Static Type System

這點對團隊來說非常重要,每個人寫 Code 的品質真的差異極大,所以每次寫 Function Test 都要寫如果變數帶數字 1 或字串 1,都要寫測試,在函示內也要進行轉換,免的程式出錯,這點解決了大部份工程師會犯錯的問題,並不是每個工程師寫 Code 都會使用 !=====

3. Performance!!

效能這點就無庸置疑,直接看這連結 Go 1.8 vs Python 3.6.0

4. You Don’t Need Web Framework For Go

可以先看去年這篇『Why I Don’t Use Go Web Frameworks』,如果想寫 web 服務,要最好的效能,就是不要引用複雜第三方套件,直接用 Go 內建的 Package 最快,當然如果是跟其他語言的 Framework 比起來(像是 Django 或 PHP 的 Laravel),開發速度不會比較快,但是得到的就是好的效能以及上述優勢。

在 DigitalOcean 新竹社群簡介 Gitea 開源專案

$
0
0
gitea 很高興受到 DigitalOcean 新竹社群邀請來介紹輕量級的 Git 服務: Gitea,在不久之前筆者已經寫過一篇 Gitea 介紹,這次到交通大學宣傳這套免費的開源專案,目的就是希望台灣有更多開發者或企業可以了解用 Go 語言也可以打造一套輕量級 Git 服務,並且導入台灣的新創團隊。這次分享是透過 DigitalOcean 最小機器 (512MB 記憶體,每個月五美金) 來 Demo 如何在 Ubuntu 16.04 快速架設 Gitea 及使用 Caddy 來自動申請 Let’s Encrypt 憑證,最後搭配 Jenkins 串自動化部署及測試等…。 2017-04-23-18-18-45

投影片

底下是這次 Gitea 介紹投影片,使用底下服務或開源專案 A painless self-hosted Git service: Gitea from Bo-Yi Wu 課程中上我也給大家看 Gitea 在 Ubuntu 底下耗了多少記憶體:大約在 40 ~ 60 MB 所以開個最小機器也是綽綽有餘。 Screen Shot 2017-04-22 at 11.17.41 PM 最後來個工商服務,如果大家想學習 Go 語言,可以報名我在 iThome 的課程: 『Go 語言一天就上手』,可以由此連結進行報名。

後記

這次聚會遇到好多大神,像是 DK 大大保哥BlueTHackmd 作者 Max,難得大家來到新竹聚聚,感覺真的很棒,最後感謝 Peter (CDNJS Maintainer) 的主辦及工作團隊,活動真的很棒。

用 Go 語言打造 DevOps Bot

$
0
0
18190989_10210525473186864_1567687746_n 在 4/27 參加 iThome 舉辦的第一屆 ChatBot Day,我分享了如何用 Go 語言 實作 DevOps Bot,可以透過 Facebook MessengerLine Messenger API 來主動通知開發者。此議程希望可以幫助想玩 Bot 但是又不知道如何入門的開發者。如果不懂程式語言,也可以直些下載 Binary 來玩玩看。

DevOps Bot 需要哪些功能

  • 支援 Command Line Flag 參數功能
  • 支援 Bot API WebHook 功能
  • 支援 Https for WebHook Tunnel
  • 支援自動更新 https 憑證功能 (Let’s Encrypt)
  • 支援監控 WebHook 服務功能
  • 支援多種訊息格式 (圖片, 影片, 表情符號 … 等)
  • 支援跨平台編譯執行檔
  • 支援透過 Docker 發送訊息
  • 支援高並發 (處理大量發送訊息)
有興趣可以直接看投影片說明: 大家可以直接下載 drone-linedrone-facebook 執行檔來玩玩。

用 Docker Multi-Stage 編譯出 Go 語言最小 Image

$
0
0
docker 之前應該沒寫過用 Docker 結合 Go 語言編譯出最小 Image 的文章,剛好趁這機會來介紹。其實網路上可以直接找到文章,像是這篇『Building Minimal Docker Containers for Go Applications』,那本文來介紹 Docker 新功能 multi-stage builds,此功能只有在 17.05.0-ce 才支援,看起來是 2017/05/03 號會 release 出來。我們拿 Go 語言的 Hello World 來介紹 Single build 及 Multiple build。

Single build

底下是 Go 語言 Hello World 範例:
package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}
接著用 alpine 的 Go 語言 Image 來編譯出執行檔。
FROM golang:alpine
WORKDIR /app
ADD . /app
RUN cd /app && go build -o app
ENTRYPOINT ./app
接著執行底下編譯指令:
$ docker build -t appleboy/go-app .
$ docker run --rm appleboy/go-app
最後檢查看看編譯出來的 Image 大小,使用 docker images | grep go-app,會發現 Image 大小為 258 MB

Multiple build

Multiple build 則是可以在 Dockerfile 使用多個不同的 Image 來源,請看看底下範例
# build stage
FROM golang:alpine AS build-env
ADD . /src
RUN cd /src && go build -o app

# final stage
FROM alpine
WORKDIR /app
COPY --from=build-env /src/app /app/
ENTRYPOINT ./app
從上面可以看到透過 AS--from 互相溝通,以前需要寫兩個 Dockerfile,現在只要一個就可以搞定。最後一樣執行編譯指令:
$ docker build -t appleboy/go-app .
$ docker run --rm appleboy/go-app
會發現最後大小為 6.35 MB,比上面是不是小了很多。

最小 Image?

6.35 MB 是最小的 Image 了嗎?才單單一個 Hello World 執行檔,用 Docker 包起來竟然要 6.35,其實不用這麼大,我們可以透過 Dokcer 所提供的最小 Image: scratch,將執行檔直接包進去即可,在編譯執行檔需加入特定參數才可以:
$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app
再透過 Docker 包起來
FROM centurylink/ca-certs

ADD app /

ENTRYPOINT ["/app"]
編譯出來大小為: 1.81MB,相信這是最小的 Image 了。

結論

Multiple build 非常方便,這樣就可以將多個步驟全部合併在一個 Dockerfile 處理掉,像是底下例子
from debian as build-essential
arg APT_MIRROR
run apt-get update
run apt-get install -y make gcc
workdir /src

from build-essential as foo
copy src1 .
run make

from build-essential as bar
copy src2 .
run make

from alpine
copy --from=foo bin1 .
copy --from=bar bin2 .
cmd ...
用一個 Dockerfile 產生多個執行檔,最後再用 alpine 打包成 Image。

附上本篇程式碼範例

Go 語言內 struct methods 該使用 pointer 或 value 傳值?

$
0
0
Go-brown-side.sh 上週末在台北講『Go 語言基礎課程』,其中一段介紹 Struct 的使用,發現有幾個學員對於在 Method 內要放 Pointer 或 Value 感到困惑,而我自己平時在寫 Go 語言也沒有注意到這點。好在強者學員 Dboy Liao 找到一篇說明:『Don’t Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang』,在 Go 語言如何區分 func (s *MyStruct)func (s MyStruct),底下我們先來看看簡單的 Struct 例子
package main

import "fmt"

type Cart struct {
    Name  string
    Price int
}

func (c Cart) GetPrice() {
    fmt.Println(c.Price)
}


func main() {
    c := &Cart{"bage", 100}
    c.GetPrice()
}
上面是個很簡單的 Go struct 例子,假設我們需要動態更新 Price 值,可以新增 UpdatePrice method。線上執行範例
package main

import "fmt"

type Cart struct {
    Name  string
    Price int
}

func (c Cart) GetPrice() {
    fmt.Println("price:", c.Price)
}

func (c Cart) UpdatePrice(price int) {
    c.Price = price
}

func main() {
    c := &Cart{"bage", 100}
    c.GetPrice()
    c.UpdatePrice(200)
    c.GetPrice()
}
上面可以看到輸出的結果是 100,只用 value 傳值是無法改 Struce 內成員。我們可以用另外方式繞過。線上執行範例
package main

import "fmt"

type Cart struct {
    Name  string
    Price int
}

func (c Cart) GetPrice() {
    fmt.Println("price:", c.Price)
}

func (c Cart) UpdatePrice(price int) *Cart {
    c.Price = price
    return &c
}

func main() {
    c := &Cart{"bage", 100}
    c.GetPrice()
    c = c.UpdatePrice(200)
    c.GetPrice()
}
從上面範例可以發現,將 struct 回傳,這樣就可以正確拿到修改的值。但是這解法不是我們想要的。來試試看用 Pointer 方式 線上執行範例
package main

import "fmt"

type Cart struct {
    Name  string
    Price int
}

func (c Cart) GetPrice() {
    fmt.Println("price:", c.Price)
}

func (c Cart) UpdatePrice(price int) {
    fmt.Println("[value] Update Price to", price)
    c.Price = price
}

func (c *Cart) UpdatePricePointer(price int) {
    fmt.Println("[pointer] Update Price to", price)
    c.Price = price
}

func main() {
    c := &Cart{"bage", 100}
    c.GetPrice()
    c.UpdatePrice(200)
    fmt.Println(c)
    c.UpdatePricePointer(200)
    fmt.Println(c)
}
只要使用 pointer 方式傳值就可以正確將您需要改變的值寫入,所以這邊可以結論就是,如果只是要讀值,可以使用 Value 或 Pointer 方式,但是要寫入,則只能用 Pointer 方式。其實在 Go 語言官方有整理 FAQ,竟然之前都沒發現,參考底下官方給的建議。

寫入或讀取

如果您需要對 Struct 內的成員進行修改,那請務必使用 Pointer 傳值,相反的,Go 會使用 Copy struct 方式來傳入,但是用此方式你就拿不到修改後的資料。

效能

假設 Struct 內部成員非常的多,請務必使用 Pointer 方式傳入,這樣省下的系統資源肯定比 Copy Value 的方式還來的多。

一致性

在開發團隊內,如果有人使用 Pointer 有人使用 Value 方式,這樣寫法不統一,造成維護效率非常低,所以官方建議,全部使用 Pointer 方式是最好的寫法。

減少 node_modules 大小來加速部署 Node.js 專案

$
0
0
yarn-kitten-full 相信 Node.js 開發者現在大部分都在使用 Yarn 了吧?如果還不知道或無法體會 Yarn 帶來的好處可以參考之前寫的一篇『用 Yarn 取代 Npm 管理 JavaScript 套件版本』,帶你體會 yarn install vs npm install 的速度差異。本篇最主要會介紹在部署 Node.js 專案都需要把 node_modules 壓縮一起丟到遠端伺服器 (假設你不是用 Docker 部署),這時候來聊聊怎麼減少 node_modules 大小。

傳統 npm 作法

Yarn 尚未出來時,可以透過 npm prune --production 的方式來將 devDependencies 內的套件全部清除
$ npm install
$ .... 處理其他事情
$ npm prune --production
$ .... 最後將 node_modeuls 打包

yarn 作法

原本我是把 yarn 搭配 npm prune --production,在早期的 yarn 版本似乎不會有問題,但是發現最新版本 npm prune 會把非 devDependencies 內的套件也一併清除,雖然 yarn 沒有提供 prune 指令,但是有個 flag 可以使用,效果跟 npm prune 一樣,就是加上 --production 這樣就可以降低不少大小,所以部署流程會變成底下
$ yarn install
先是安裝全部套件 (像是 babel-cli),接著透過 babel 轉換程式碼,最後在下底下指令
$ yarn install --production
這時候就會把 babel 相關套件全部移除,最後將 node_modules 打包就可以了。

結論

除了上述過程外,在 CI/CD 流程內,務必設定 yarn 快取目錄在專案內。
$ yarn config set cache-folder .yarn-cache
跑完部屬流程後,可以把 .yarn-cachenode_modules 同時打包,等到下次跑 CI/CD 時會加速不少喔。

Node.js 8 搭配 npm 5 速度

$
0
0
yarn-kitten-full 這個月 Node.js 釋出 8.0 版本,搭配的就是 npm v5.0.0 版本,上一篇寫到如何透過 Yarn 指令移除 devDependencies 內的 Package 套件,減少 node_modules 大小,有網友提到那 npm 5 的速度為何?其實筆者已經好久沒有用 npm 了,但是有人提問,我就立馬來測試看看 npm vs yarn 的速度,詳細數據可以參考此專案說明。測試方法如下

設定環境

底下是測試環境版本
  • node version: v8.0.0
  • npm verison: 5.0.0
  • yarn verison: 0.24.6

初次安裝 (沒有任何快取)

先把 node_modules 刪除,及系統相關 Cache 目錄都移除
$ npm cache verify
$ rm -rf ~/.npm/_cacache/
$ time npm install
npm 花費時間: 1m43.370s
$ yarn cache clean
$ time yarn install
yarn 花費時間: 1m1.707s

保留系統快取目錄

執行完第一次安裝後,我們把 node_modules 移除後再安裝一次試試看
$ rm -rf node_moduels
$ time npm install
npm 花費時間: 0m38.819s
$ rm -rf node_moduels
$ time yarn install
yarn 花費時間: 0m24.219s

保留系統快取目錄及 node_modules

最後保留 node_modules 目錄,再執行一次安裝
$ time npm install
npm 花費時間: 0m11.216s
$ time yarn install
yarn 花費時間: 0m0.954s

結論

大家可以發現,雖然 npm 改進不少速度,但是 Yarn 還是優勝許多,這邊可以總結,已經在使用 yarn 的,可以繼續使用,尚未使用 yarn 的開發者,可以嘗試使用看看。另外 npm 5 現在執行 npm install –save 會預設產生出 package-lock.json 跟 yarn 產生的 yarn.lock 是類似的東西,除非專案內已經有 npm-shrinkwrap.json,否則 npm 會自動幫專案產生喔。詳細情形可以看 Replace metadata + fetch + cache code。npm cache 指令可以看此文件

Cronjob 搭配 Drone 服務

$
0
0
drone-logo_512 Drone 是一套基於 Docker Container 技術的 CI/CD 服務,它是用 Go 語言所開發,可以安裝在任何作業系統內,你可以把 Drone 當作是開源版的 Travis 服務。Drone 本身不支援排程任務,也就是說無法像 Jenkins CI 一樣可以設定每天幾點幾分執行單一 Job 任務。但是可以透過第三方工具像是 cron 來整合 Drone API 達成自動排程的效果,底下來看看該如何實作。

安裝 Drone CLI

Drone 提供 CLI 工具,讓開發者可以快速跟 Drone 服務溝通,底下兩種方式來安裝 Drone CLI。從官網找相對應作業系統的執行檔
  • Linux x64
  • Windows x64
  • Darwin x64
另外一種方式則是透過 go get 方式來安裝,前提是您必須要安裝 Go 語言環境
$ go get github.com/drone/drone-cli/drone

Drone CLI 教學

下面指令是透過 CLI 呼叫 Drone 執行指定的專案 Job Number。如果沒有提供 Number 編號,則是執行該專案最後一個 Build Number。
$ drone build start --fork <repository> <build>
--fork 代表啟動新的任務,並非是重新啟動該編號任務。下面指令則是根據專案 Branch 名稱得到最後 Build Number。
$ drone build last --format="{{ .Number }}" \
  --branch=<branch> <repository>
拿到最後一個 Number 後,就可以開始寫 Cron job 任務

整合 cron job

從上面教學可以知道如何透過 Drone CLI 拿到專案最後執行的 Job 任務編號,以及如何重新執行專案任務,這時我們可以將指令合併成一行,變且寫進 crontab -e 檔案內
* 22 * * * drone build start --fork octocat/hello-world \
  $(drone build last --format="{{ .Number }}" \
  --branch=master octocat/hello-world)
branchoctocat/hello-world 換成您的專案名稱即可。

結論

用 crontab + drone cli 就可以完成 Jenkins 可以做到的事情。這樣真的可以完全捨棄 Jenkins 了。如果大家對 Drone 有興趣,想更深入了解,可以來報名『用一天打造團隊自動化測試及部署』,此課程會在一天內帶您進入自動化測試及部署,想從 Jenkins 或 GitLab CI 轉換到 Drone 的,歡迎報名參加。
  • 時間: 2017/07/29 09:30 ~ 17:30
  • 地點: CLBC 大安館 (台北市大安區區復興南路一段283號4樓)
  • 價格: 3990 元

報名連結

Drone 自動觸發 GitLab CI 或 Jenkins 任務

$
0
0
drone-logo_512 Drone 是一套由 Go 語言所開發的開源碼專案,讓開發者可以使用 Docker Container 快速設定自動化測試及部署,上篇有提到『Cronjob 搭配 Drone 服務』,讓 JenkinsGitLab CI 用戶可以轉換 Cron Job 任務到 Drone 上面。本篇則是會介紹如何透過 Drone 去觸發 Jenkins 或 GitLab CI 上的工作,當然這是過渡時期,希望大家最後能將工作完整移轉到 Drone 上面,不要再依靠 Jenkins 或 GitLab CI 了。本篇會教大家用三種方式來觸發 GitLab CI 或 Jenkins 任務。
  • 使用 Drone CI/CD
  • 使用 Docker 指令
  • 使用 Command Line (命令列)

觸發 GitLab CI

不管是 GitLab CI 或是 Jenkins 都可以透過該服務提供的 HTTP API 來遠端觸發,而我用 Go 語言將觸發這動作寫成 Drone Plugin,讓 Drone 用戶可以不用自己寫 curl 去觸發。

申請專案 Token

要透過 HTTP API 去觸發任務,首先就是要申請專案 Token,請大家參考 Triggering pipelines through the API 頁面。申請完成後,請把專案 ID 跟 Token 帶入底下使用。

使用 Drone 觸發

pipeline:
  gitlab:
    image: appleboy/drone-gitlab-ci
    host: https://gitlab.com
    token: xxxxxxxxxx
    ref: master
    id: gitlab-project-id
其中 host 請改成公司內部的 Git 伺服器網址,id 是專案獨立 ID,最後 Token 則是上面步驟所拿到的 Token。詳細設定可以參考 README,如果不是用 Drone 也沒關係,可以用 Docker 或透過 Go 語言可以包成各作業系統執行檔 (包含 Windows)

使用 Docker 觸發

請使用 appleboy/drone-gitlab-ci 映像檔,檔案大小為 2MB
docker run --rm \
  -e GITLAB_HOST=https://gitlab.com/
  -e GITLAB_TOKEN=xxxxx
  -e GITLAB_REF=master
  -e GITLAB_ID=gitlab-ci-project-id
  appleboy/drone-gitlab-ci

使用 CLI 觸發

請先從 Release 頁面下載相關執行檔,重點是你也可以在 Windows 做到此事情喔 (這就是 Go 語言跨平台的好處)。在命令列使用底下指令。
drone-gitlab-ci \
  --host https://gitlab.com/ \
  --token XXXXXXXX \
  --ref master \
  --id gitlab-ci-project-id

測試看看

這邊提供 GitLab 專案的資料給大家直接測試看看
drone-gitlab-ci \
  --host https://gitlab.com \
  --token 9184302d980918efad05bce8b97774 \
  --ref master \
  --id 3573921
上面指令沒意外的話,會看到底下結果:
2017/06/27 15:01:59 build id: 9360879
2017/06/27 15:01:59 build sha: 169e7c1d798c9593c06fbd9d474da9c07f699634
2017/06/27 15:01:59 build ref: master
2017/06/27 15:01:59 build status: pending
直接到 pipeline 頁面看結果 Screen Shot 2017-06-27 at 3.02.05 PM

歡迎大家關注此專案 drone-gitlab-ci

接著來講 Jenkins 部分,其實原理都跟 Gitlab CI 是一樣的。

觸發 Jenkins

這邊其實跟 GitLab CI 設定相同,只是參數不一樣而已,首先必須要去哪裡找個人 API Token,請到 Jenkins 個人頁面找到 API Token。 Screen Shot 2017-06-27 at 3.11.29 PM

使用 Drone 觸發

pipeline:
  jenkins:
    image: appleboy/drone-jenkins
    url: http://example.com
    user: appleboy
    token: xxxxxxxxxx
    job: drone-jenkins-plugin-job
其中 url 請改成公司內部的 Jenkins 伺服器網址,job 是 Jenkins 任務名稱,最後 Token 則是上面個人帳號所拿到的 Token。詳細設定可以參考 README,如果不是用 Drone 也沒關係,可以用 Docker 或透過 Go 語言可以包成各作業系統執行檔 (包含 Windows)

使用 Docker 觸發

請使用 appleboy/drone-jenkins 映像檔,檔案大小為 2MB
docker run --rm \
  -e JENKINS_BASE_URL=http://jenkins.example.com/
  -e JENKINS_USER=appleboy
  -e JENKINS_TOKEN=xxxxxxx
  -e JENKINS_JOB=drone-jenkins-plugin
  appleboy/drone-jenkins

使用 CLI 觸發

請先從 Release 頁面下載相關執行檔,重點是你也可以在 Windows 做到此事情喔 (這就是 Go 語言跨平台的好處)。在命令列使用底下指令。
drone-jenkins \
  --host http://jenkins.example.com/ \
  --user appleboy \
  --token XXXXXXXX \
  --job drone-jenkins-plugin

歡迎大家關注此專案 drone-jenkins

總結

寫這兩個 Plugin 的目的就是希望能有多點開發者從 Jenkins 或 GitLab CI 轉到 Drone,這是過渡時期,等熟悉了 Drone 的設定,你會發現 Drone 已經可以做到 Jenkins 或 GitLab CI 所有事情,甚至更強大。如果對 Drone 有興趣,可以來上七月底的『用一天打造團隊自動化測試及部署
  • 時間: 2017/07/29 09:30 ~ 17:30
  • 地點: CLBC 大安館 (台北市大安區區復興南路一段283號4樓)
  • 價格: 3990 元

報名連結

台灣第一屆 GoPher 大會

$
0
0
Screen Shot 2017-06-30 at 10.47.50 AM 很高興可以擔任第一屆 GoPher Day 大會講者,每次參加聚會都是跟一堆網友見面,人在新竹很難得大家見到面。很感謝 iThome 大力幫忙舉辦,才可以讓整天議程順利完成。底下分享『用 Go 語言實戰 Push Notification 服務』投影片。

投影片重點大綱

  • Gorush 介紹
  • Gorush 內部 Goroutine 實作
  • Gorush API 實作
  • 用 Drone 打造 Go 語言部署及測試流程
最後花了一些時間介紹 Drone 這套用 Go 語言撰寫的 CI/CD 工具,如果大家有興趣可以參加 7 月底的 iThome 課程『用一天打造團隊自動化測試及部署
  • 時間: 2017/07/29 09:30 ~ 17:30
  • 地點: CLBC 大安館 (台北市大安區區復興南路一段283號4樓)
  • 價格: 3990 元

台灣第一屆 Laravel 研討會

$
0
0
laravelconftw_o 這次很高興擔任第一屆 Laravel 台灣研討會講者,會議當天中午才到現場,我是兩點分享的議程,在整天聽下來及最後的案例討論,聽到最多的都是原本從 CodeIgniter 架構換到 Laravel 上面,身為 CodeIgniter 的維護人員的我,聽到是蠻開心的,在 Laravel 還沒出來前,大家都是選用這輕量級的 CodeIgniter。

簡報分享

在 Laravel 會議內,我是分享去年在公司內部使用 Laravel 來跟同事間團隊合作的經驗,題目是『運用 Docker 整合 Laravel 提升團隊開發效率』,大家可以參考當天的共筆紀錄,底下整理投影片大綱:
  1. 五大不用 Homestead 的理由
  2. 七大 Docker 必學指令
  3. Laradock 開源專案介紹
  4. 導入 Docker 後的優勢
  5. 用 Drone 整合測試及部署
底下是投影片,投影片內的範例可以參考

drone-laravel-example

最後花了一些時間介紹 Drone 這套用 Go 語言撰寫的 CI/CD 工具,如果大家有興趣可以參加 7 月底的 iThome 課程『用一天打造團隊自動化測試及部署』。
  • 時間: 2017/07/29 09:30 ~ 17:30
  • 地點: CLBC 大安館 (台北市大安區區復興南路一段283號4樓)
  • 價格: 3990 元

心得

真心覺得這次 Laravel 台灣研討會辦得真是很棒,場地地點也離台北火車站不遠,讓我可以快速的從新竹過來。現場也體會到 LaravelConf 台灣團隊的用心,講師一上台,馬上就有一張照片出現在 Facebook 官網,並且分享共筆連結,讓現場或者是網路的朋友可以跟上進度,真的很棒,另外講師休息室的風景真是太美了。總之辦得真的很棒,感謝 Laravel 傳教士兼靈魂人物 Shengyou Fan 及團隊。 laravelconftw

Go 語言框架 Gin 終於發佈 v1.2 版本

$
0
0
19807878_1634683919888714_743883353_o 上週跟 Gin 作者 @javierprovecho 討論要發佈新版本,很快地經過一兩天,作者終於整理好 v1.2 版本,除了釋出新版本外,也換了有顏色的 Logo,真心覺得很好看。大家來看看 v1.2 釋出哪些功能,或修正哪些問題。

如何升級

首先來看看如何升級版本,建議還沒有用 vendor 工具的開發者,是時候該導入了。底下可以透過 govender 來升級 Gin 框架。
$ govendor fetch github.com/gin-gonic/gin@v1.2
$ govendor fetch github.com/gin-gonic/gin/render
由於我們新增 Template Func Maps,所以 render 套件也要一併升級喔。

從 godeps 轉換到 govender

Gin 專案本來是用 godeps,但是在套件處理上有些問題,所以我們決定換到穩定些的 govender,看看之後 Go 團隊開發的 dep 可不可以完全取代掉 govendor。

支援 Let’s Encrypt

我另外開一個專案 autotls 讓 Gin 也可以支援 Let’s Encrypt,這專案可以用在 net/http 套件上,所以基本上支援全部框架,除非搭建的 Http Server 不是用 net/http。使用方式很簡單,如下:

用一行讓 Web 支援 TLS

package main

import (
    "log"

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

func main() {
    r := gin.Default()

    // Ping handler
    r.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })

    log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
}

自己客製化 Auto TLS Manager

開發者可以將憑證存放在別的目錄,請修改 /var/www/.cache
package main

import (
    "log"

    "github.com/gin-gonic/autotls"
    "github.com/gin-gonic/gin"
    "golang.org/x/crypto/acme/autocert"
)

func main() {
    r := gin.Default()

    // Ping handler
    r.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })

    m := autocert.Manager{
        Prompt:     autocert.AcceptTOS,
        HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
        Cache:      autocert.DirCache("/var/www/.cache"),
    }

    log.Fatal(autotls.RunWithManager(r, &m))
}

支援 Template Func 功能

首先讓開發者可以調整 template 分隔符號,原本是用 {{}},現在可以透過 Gin 來設定客製化符號。
    r := gin.Default()
    r.Delims("{[{", "}]}")
    r.LoadHTMLGlob("/path/to/templates"))
另外支援 Custom Template Funcs
    ...

    func formatAsDate(t time.Time) string {
        year, month, day := t.Date()
        return fmt.Sprintf("%d/%02d/%02d", year, month, day)
    }

    ...

    router.SetFuncMap(template.FuncMap{
        "formatAsDate": formatAsDate,
    })

    ...

    router.GET("/raw", func(c *Context) {
        c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
            "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
        })
    })

    ...
打開 raw.tmpl 寫入
Date: {[{.now | formatAsDate}]}
執行結果:
Date: 2017/07/01

增加 Context 函式功能

在此版發佈前,最令人煩惱的就是 Bind Request Form 或 JSON 驗證,因為 Gin 會直接幫忙回傳 400 Bad Request,很多開發者希望可以自訂錯誤訊息,所以在 v1.2 我們將 BindWith 丟到 deprecated 檔案,並且打算在下一版正式移除。
// BindWith binds the passed struct pointer using the specified binding engine.
// See the binding package.
func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
    log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
    be deprecated, please check issue #662 and either use MustBindWith() if you
    want HTTP 400 to be automatically returned if any error occur, of use
    ShouldBindWith() if you need to manage the error.`)
    return c.MustBindWith(obj, b)
}
如果要自訂訊息,請用 ShouldBindWith
package main

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

type LoginForm struct {
    User     string `form:"user" binding:"required"`
    Password string `form:"password" binding:"required"`
}

func main() {
    router := gin.Default()
    router.POST("/login", func(c *gin.Context) {
        // you can bind multipart form with explicit binding declaration:
        // c.MustBindWith(&form, binding.Form)
        // or you can simply use autobinding with Bind method:
        var form LoginForm
        // in this case proper binding will be automatically selected
        if c.ShouldBindWith(&form) == nil {
            if form.User == "user" && form.Password == "password" {
                c.JSON(200, gin.H{"status": "you are logged in"})
            } else {
                c.JSON(401, gin.H{"status": "unauthorized"})
            }
        }
    })
    router.Run(":8080")
}
大致上是這些大修正,剩下的小功能或修正,請直接參考 v1.2 releases log
Viewing all 325 articles
Browse latest View live