<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>小惡魔 &#8211; 電腦技術 &#8211; 工作筆記 &#8211; AppleBOY</title>
	<atom:link href="https://blog.wu-boy.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.wu-boy.com</link>
	<description>介紹一堆美食日記，生活日記，電腦筆記</description>
	<lastBuildDate>Sat, 29 May 2021 09:25:12 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.8.3</generator>
	<item>
		<title>如何取得上傳進度條 progress bar 相關數據及實作 Graceful Shutdown</title>
		<link>https://blog.wu-boy.com/2021/05/graceful-shutdown-with-progress-bar-in-golang/</link>
		<comments>https://blog.wu-boy.com/2021/05/graceful-shutdown-with-progress-bar-in-golang/#respond</comments>
		<pubDate>Fri, 21 May 2021 04:52:17 +0000</pubDate>
		<dc:creator><![CDATA[appleboy]]></dc:creator>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[channel]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[graceful shutdown]]></category>
		<category><![CDATA[reader]]></category>

		<guid isPermaLink="false">https://blog.wu-boy.com/?p=7940</guid>
		<description><![CDATA[由於專案需求，需要開發一套 CLI 工具，讓 User 可以透過 CLI 上傳大檔案來進行 Model Training，請參考上面的流程圖。首先第一步驟會先跟 API Server 驗證使用者，驗證完畢就開始上傳資料到 AWS S3 或其他 Storage 空間，除了上傳過程需要在 CLI 顯示目前進度，另外也需要將目前上傳的進度 (速度, 進度及剩餘時間) 都上傳到 API Server，最後在 Web UI 介面透過 GraphQL Subscription 讓使用者可以即時看到上傳進度數據。 而 CLI 上傳進度部分，我們選用了一套開源套件 cheggaaa/pb，相信有在寫 Go 語言都並不會陌生。而此套件雖然可以幫助在 Terminal 顯示進度條，但是有些接口是沒有提供的，像是即時速度，上傳進度及剩餘時間。本篇教大家如何實作這些數據，及分享過程會遇到相關問題。 讀取上傳進度顯示 透過 cheggaaa/pb 提供的範例如下: package main import ( &#34;crypto/rand&#34; &#34;io&#34; &#34;io/ioutil&#34; &#34;log&#34; &#34;github.com/cheggaaa/pb/v3&#34; ) func main() { &#8230; <a href="https://blog.wu-boy.com/2021/05/graceful-shutdown-with-progress-bar-in-golang/" class="more-link">Continue reading<span class="screen-reader-text"> "如何取得上傳進度條 progress bar 相關數據及實作 Graceful Shutdown"</span></a>]]></description>
				<content:encoded><![CDATA[<p><img src="https://lh3.googleusercontent.com/ASkclquxfpPTlJ_QWnhZjB5katrz18NyK4zt2w47UM8gS71MCjWodDoGp50nHRyeQx8MfbJJbwWjfIWCoKbZYLkec7a-FqMEw-r9Lh3U8XGAuwEqWa3DVMB2lkhdgMQUI1IMiKWL5Ss=w1920-h1080" alt="" /></p>
<p>由於專案需求，需要開發一套 CLI 工具，讓 User 可以透過 CLI 上傳大檔案來進行 Model Training，請參考上面的流程圖。首先第一步驟會先跟 API Server 驗證使用者，驗證完畢就開始上傳資料到 <a href="https://aws.amazon.com/tw/s3/">AWS S3</a> 或其他 Storage 空間，除了上傳過程需要在 CLI 顯示目前進度，另外也需要將目前上傳的進度 (速度, 進度及剩餘時間) 都上傳到 API Server，最後在 Web UI 介面透過 <a href="https://www.apollographql.com/docs/react/data/subscriptions/">GraphQL Subscription</a> 讓使用者可以即時看到上傳進度數據。</p>
<p>而 CLI 上傳進度部分，我們選用了一套開源套件 <a href="https://github.com/cheggaaa/pb">cheggaaa/pb</a>，相信有在寫 <a href="https://golang.org">Go 語言</a>都並不會陌生。而此套件雖然可以幫助在 Terminal 顯示進度條，但是有些接口是沒有提供的，像是即時速度，上傳進度及剩餘時間。本篇教大家如何實作這些數據，及分享過程會遇到相關問題。</p>
<span id="more-7940"></span>
<h2>讀取上傳進度顯示</h2>
<p>透過 <a href="https://github.com/cheggaaa/pb">cheggaaa/pb</a> 提供的範例如下:</p>
<pre><code class="language-go">package main

import (
    &quot;crypto/rand&quot;
    &quot;io&quot;
    &quot;io/ioutil&quot;
    &quot;log&quot;

    &quot;github.com/cheggaaa/pb/v3&quot;
)

func main() {

    var limit int64 = 1024 * 1024 * 10000
    // we will copy 10 Gb from /dev/rand to /dev/null
    reader := io.LimitReader(rand.Reader, limit)
    writer := ioutil.Discard

    // start new bar
    bar := pb.Full.Start64(limit)
    // create proxy reader
    barReader := bar.NewProxyReader(reader)
    // copy from proxy reader
    if _, err := io.Copy(writer, barReader); err != nil {
        log.Fatal(err)
    }
    // finish bar
    bar.Finish()
}</code></pre>
<p>很清楚可以看到透過 <code>io.Copy</code> 方式開始上傳模擬進度。接著需要透過 <code>goroutine</code> 方式讀取目前進度並上傳到 API Server。</p>
<h2>計算上傳進度及剩餘時間</h2>
<p>使用 pb v3 版本只有開放幾個 public 資訊，像是起始進度時間，及目前上傳了多少 bits 資料，透過這兩個資料，可以即時算出剩餘時間，目前速度及進度。</p>
<pre><code class="language-go">package main

import (
    &quot;crypto/rand&quot;
    &quot;fmt&quot;
    &quot;io&quot;
    &quot;io/ioutil&quot;
    &quot;log&quot;
    &quot;time&quot;

    &quot;github.com/cheggaaa/pb/v3&quot;
)

func main() {
    var limit int64 = 1024 * 1024 * 10000
    // we will copy 10 Gb from /dev/rand to /dev/null
    reader := io.LimitReader(rand.Reader, limit)
    writer := ioutil.Discard

    // start new bar
    bar := pb.Full.Start64(limit)
    go func(bar *pb.ProgressBar) {
        d := time.NewTicker(2 * time.Second)
        startTime := bar.StartTime()
        // Using for loop
        for {
            // Select statement
            select {
            // Case to print current time
            case &lt;-d.C:
                if !bar.IsStarted() {
                    continue
                }
                currentTime := time.Now()
                dur := currentTime.Sub(startTime)
                lastSpeed := float64(bar.Current()) / dur.Seconds()
                remain := float64(bar.Total() - bar.Current())
                remainDur := time.Duration(remain/lastSpeed) * time.Second
                fmt.Println(&quot;Progress:&quot;, float32(bar.Current())/float32(bar.Total())*100)
                fmt.Println(&quot;last speed:&quot;, lastSpeed/1024/1024)
                fmt.Println(&quot;remain duration:&quot;, remainDur)

                // TODO: upload progress and remain duration to api server
            }
        }
    }(bar)
    // create proxy reader
    barReader := bar.NewProxyReader(reader)
    // copy from proxy reader
    if _, err := io.Copy(writer, barReader); err != nil {
        log.Fatal(err)
    }
    // finish bar
    bar.Finish()
}</code></pre>
<p>使用 <code>time.NewTicker</code> 固定每兩秒計算目前進度資料，並且上傳到 API Server，從上傳資料及使用的時間，可以算出目前 Speed 大概多少，當然這不是很準，原因是從上傳開始到現在時間計算 (總已上傳資料/目前花費時間)。</p>
<h2>使用 Channel 結束上傳</h2>
<p>做完上述這些功能，不難的發現有個問題，這個 goroutine 不會停止，還是會每兩秒去計算進度，這時候需要透過一個 Channel 通知 goroutine 結束。</p>
<pre><code class="language-go">package main

import (
    &quot;crypto/rand&quot;
    &quot;fmt&quot;
    &quot;io&quot;
    &quot;io/ioutil&quot;
    &quot;log&quot;
    &quot;time&quot;

    &quot;github.com/cheggaaa/pb/v3&quot;
)

func main() {
    var limit int64 = 1024 * 1024 * 10000
    // we will copy 10 Gb from /dev/rand to /dev/null
    reader := io.LimitReader(rand.Reader, limit)
    writer := ioutil.Discard

    // start new bar
    bar := pb.Full.Start64(limit)
    finishCh := make(chan struct{})
    go func(bar *pb.ProgressBar) {
        d := time.NewTicker(2 * time.Second)
        startTime := bar.StartTime()
        // Using for loop
        for {
            // Select statement
            select {
            case &lt;-finishCh:
                d.Stop()
                log.Println(&quot;finished&quot;)
                return
            // Case to print current time
            case &lt;-d.C:
                if !bar.IsStarted() {
                    continue
                }
                currentTime := time.Now()
                dur := currentTime.Sub(startTime)
                lastSpeed := float64(bar.Current()) / dur.Seconds()
                remain := float64(bar.Total() - bar.Current())
                remainDur := time.Duration(remain/lastSpeed) * time.Second
                fmt.Println(&quot;Progress:&quot;, float32(bar.Current())/float32(bar.Total())*100)
                fmt.Println(&quot;last speed:&quot;, lastSpeed/1024/1024)
                fmt.Println(&quot;remain suration:&quot;, remainDur)
            }
        }
    }(bar)
    // create proxy reader
    barReader := bar.NewProxyReader(reader)
    // copy from proxy reader
    if _, err := io.Copy(writer, barReader); err != nil {
        log.Fatal(err)
    }
    // finish bar
    bar.Finish()
    close(finishCh)
}</code></pre>
<p>先宣告一個 <code>finishCh := make(chan struct{})</code>，用來通知 goroutine 跳出迴圈，大家注意看一下，最後是用的是關閉 Channel，如果是用底下方法:</p>
<pre><code class="language-go">finishCh &lt;- strunct{}{}</code></pre>
<p>這時候看看 switch case 有機率是同時到達，造成無法跳脫迴圈，而直接關閉 channel，可以確保 <code>case &lt;-finishCh</code> 一直拿到空的資料，進而達成跳出迴圈的需求。</p>
<h2>整合  Graceful Shutdown</h2>
<p>最後來看看如何整合 <a href="https://blog.wu-boy.com/2020/02/what-is-graceful-shutdown-in-golang/">Graceful Shutdown</a>。當使用者按下 <code>ctrl + c</code> 需要停止上傳，並將狀態改成 <code>stopped</code>。底下來看看加上 Graceful Shutdown 的方式:</p>
<pre><code class="language-go">package main

import (
    &quot;context&quot;
    &quot;crypto/rand&quot;
    &quot;fmt&quot;
    &quot;io&quot;
    &quot;io/ioutil&quot;
    &quot;log&quot;
    &quot;os&quot;
    &quot;os/signal&quot;
    &quot;syscall&quot;
    &quot;time&quot;

    &quot;github.com/cheggaaa/pb/v3&quot;
)

func withContextFunc(ctx context.Context, f func()) context.Context {
    ctx, cancel := context.WithCancel(ctx)
    go func() {
        c := make(chan os.Signal, 1)
        signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
        defer signal.Stop(c)

        select {
        case &lt;-ctx.Done():
        case &lt;-c:
            f()
            cancel()
        }
    }()

    return ctx
}

func main() {

    ctx := withContextFunc(
        context.Background(),
        func() {
            // clear machine field
            log.Println(&quot;interrupt received, terminating process&quot;)
        },
    )

    var limit int64 = 1024 * 1024 * 10000
    // we will copy 10 Gb from /dev/rand to /dev/null
    reader := io.LimitReader(rand.Reader, limit)
    writer := ioutil.Discard

    // start new bar
    bar := pb.Full.Start64(limit)
    finishCh := make(chan struct{})
    go func(ctx context.Context, bar *pb.ProgressBar) {
        d := time.NewTicker(2 * time.Second)
        startTime := bar.StartTime()
        // Using for loop
        for {
            // Select statement
            select {
            case &lt;-ctx.Done():
                d.Stop()
                log.Println(&quot;interrupt received&quot;)
                return
            case &lt;-finishCh:
                d.Stop()
                log.Println(&quot;finished&quot;)
                return
            // Case to print current time
            case &lt;-d.C:
                if ctx.Err() != nil {
                    return
                }
                if !bar.IsStarted() {
                    continue
                }
                currentTime := time.Now()
                dur := currentTime.Sub(startTime)
                lastSpeed := float64(bar.Current()) / dur.Seconds()
                remain := float64(bar.Total() - bar.Current())
                remainDur := time.Duration(remain/lastSpeed) * time.Second
                fmt.Println(&quot;Progress:&quot;, float32(bar.Current())/float32(bar.Total())*100)
                fmt.Println(&quot;last speed:&quot;, lastSpeed/1024/1024)
                fmt.Println(&quot;remain suration:&quot;, remainDur)
            }
        }
    }(ctx, bar)
    // create proxy reader
    barReader := bar.NewProxyReader(reader)
    // copy from proxy reader
    if _, err := io.Copy(writer, barReader); err != nil {
        log.Fatal(err)
    }
    // finish bar
    bar.Finish()
    close(finishCh)
}</code></pre>
<p>透過 Go 語言的 context 跟 signal.Notify 可以偵測是否有系統訊號關閉 CLI 程式，這時候就可以做後續相對應的事情，在程式碼就需要多接受 <code>ctx.Done()</code> Channel，由於在 Select 多個 Channel 通道，故也是有可能同時發生，所以需要在另外的 switch case 內判斷 conetxt 的 Err 錯誤訊息，如果不等於 nil 那就是收到訊號，進而 return，必免 goroutine 在背景持續進行。大家執行上述程式後，按下 ctrl + c 可以正常看到底下訊息:</p>
<pre><code class="language-bash">^C
2021/05/21 12:29:25 interrupt received, terminating process
2021/05/21 12:29:25 interrupt received
^C
signal: interrupt</code></pre>
<p>可以看到要在按下一次 ctrl + c 才能結束程式，這邊的原因就是 io.Reader 還是正在上傳，並沒有停止，而系統第一次中斷訊號已經被程式用掉了，這時候解決方式就是要修改底下程式</p>
<pre><code class="language-go">    barReader := bar.NewProxyReader(reader)
    // copy from proxy reader
    if _, err := io.Copy(writer, barReader); err != nil {
        log.Fatal(err)
    }</code></pre>
<h2>io.Copy 支援 context 中斷</h2>
<p><code>io.Copy</code> 需要支援 context 中斷程式，但是我們只能從 reader 下手，，先看看原本 Reader 的 interface:</p>
<pre><code class="language-go">type Reader interface {
    Read(p []byte) (n int, err error)
}</code></pre>
<p>現在來自己寫一份 func 來支援 context 功能:</p>
<pre><code class="language-go">type readerFunc func(p []byte) (n int, err error)

func (r readerFunc) Read(p []byte) (n int, err error) { return rf(p) }
func copy(ctx context.Context, dst io.Writer, src io.Reader) error {
    _, err := io.Copy(dst, readerFunc(func(p []byte) (int, error) {
        select {
        case &lt;-ctx.Done():
            return 0, ctx.Err()
        default:
            return src.Read(p)
        }
    }))
    return err
}</code></pre>
<p>由於 io.Reader 會把整個檔案分成多個 chunk 分別上傳，避免 Memory 直接讀取太大的檔案而爆掉，那在每個 chunk 上傳前確保沒有收到 context 中斷的訊息，這樣就可以解決無法停止上傳的行為。整體程式碼如下:</p>
<pre><code class="language-go">package main

import (
    &quot;context&quot;
    &quot;crypto/rand&quot;
    &quot;fmt&quot;
    &quot;io&quot;
    &quot;io/ioutil&quot;
    &quot;log&quot;
    &quot;os&quot;
    &quot;os/signal&quot;
    &quot;syscall&quot;
    &quot;time&quot;

    &quot;github.com/cheggaaa/pb/v3&quot;
)

type readerFunc func(p []byte) (n int, err error)

func (rf readerFunc) Read(p []byte) (n int, err error) { return rf(p) }

func copy(ctx context.Context, dst io.Writer, src io.Reader) error {
    _, err := io.Copy(dst, readerFunc(func(p []byte) (int, error) {
        select {
        case &lt;-ctx.Done():
            return 0, ctx.Err()
        default:
            return src.Read(p)
        }
    }))
    return err
}

func withContextFunc(ctx context.Context, f func()) context.Context {
    ctx, cancel := context.WithCancel(ctx)
    go func() {
        c := make(chan os.Signal, 1)
        signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
        defer signal.Stop(c)

        select {
        case &lt;-ctx.Done():
        case &lt;-c:
            f()
            cancel()
        }
    }()

    return ctx
}

func main() {

    ctx := withContextFunc(
        context.Background(),
        func() {
            // clear machine field
            log.Println(&quot;interrupt received, terminating process&quot;)
        },
    )

    var limit int64 = 1024 * 1024 * 10000
    // we will copy 10 Gb from /dev/rand to /dev/null
    reader := io.LimitReader(rand.Reader, limit)
    writer := ioutil.Discard

    // start new bar
    bar := pb.Full.Start64(limit)
    finishCh := make(chan struct{})
    go func(bar *pb.ProgressBar) {
        d := time.NewTicker(2 * time.Second)
        startTime := bar.StartTime()
        // Using for loop
        for {
            // Select statement
            select {
            case &lt;-ctx.Done():
                log.Println(&quot;stop to get current process&quot;)
                return
            case &lt;-finishCh:
                d.Stop()
                log.Println(&quot;finished&quot;)
                return
            // Case to print current time
            case &lt;-d.C:
                if !bar.IsStarted() {
                    continue
                }
                currentTime := time.Now()
                dur := currentTime.Sub(startTime)
                lastSpeed := float64(bar.Current()) / dur.Seconds()
                remain := float64(bar.Total() - bar.Current())
                remainDur := time.Duration(remain/lastSpeed) * time.Second
                fmt.Println(&quot;Progress:&quot;, float32(bar.Current())/float32(bar.Total())*100)
                fmt.Println(&quot;last speed:&quot;, lastSpeed/1024/1024)
                fmt.Println(&quot;remain suration:&quot;, remainDur)
            }
        }
    }(bar)
    // create proxy reader
    barReader := bar.NewProxyReader(reader)
    // copy from proxy reader
    if err := copy(ctx, writer, barReader); err != nil {
        log.Println(&quot;cancel upload data:&quot;, err.Error())
    }
    // finish bar
    bar.Finish()
    close(finishCh)
    time.Sleep(1 * time.Second)
}</code></pre>
<div class="wp_rp_wrap  wp_rp_plain" id="wp_rp_first"><div class="wp_rp_content"><h3 class="related_post_title">Related View</h3><ul class="related_post wp_rp"><li data-position="0" data-poid="in-7607" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/02/what-is-graceful-shutdown-in-golang/" class="wp_rp_title">[Go 教學] 什麼是 graceful shutdown?</a><small class="wp_rp_comments_count"> (3)</small><br /></li><li data-position="1" data-poid="in-7597" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/02/graceful-shutdown-with-multiple-workers/" class="wp_rp_title">[Go 教學] graceful shutdown with multiple workers</a><small class="wp_rp_comments_count"> (3)</small><br /></li><li data-position="2" data-poid="in-7794" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/10/select-multiple-channel-in-golang/" class="wp_rp_title">Go 語言 Select Multiple Channel 注意事項</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="3" data-poid="in-7352" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/04/install-specific-go-version-in-appveyor/" class="wp_rp_title">在 appveyor 內指定 Go 語言編譯版本</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="4" data-poid="in-6657" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/02/caddy-setting-with-drone-ci-server/" class="wp_rp_title">Caddy 搭配 Drone 伺服器設定</a><small class="wp_rp_comments_count"> (5)</small><br /></li><li data-position="5" data-poid="in-6869" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/11/gorush-a-push-notification-server-written-in-go/" class="wp_rp_title">Gorush 輕量級手機訊息發送服務</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="6" data-poid="in-7405" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/07/speed-up-go-module-download-using-go-proxy-athens/" class="wp_rp_title">架設 Go Proxy 服務加速 go module 下載速度</a><small class="wp_rp_comments_count"> (7)</small><br /></li><li data-position="7" data-poid="in-6683" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/04/1-line-letsencrypt-https-servers-in-golang/" class="wp_rp_title">在 Go 語言用一行程式碼自動化安裝且更新 Let’s Encrypt 憑證</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="8" data-poid="in-6198" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/04/gofight-tool-for-api-handler-testing-in-golang/" class="wp_rp_title">用 gofight 來測試 golang web API handler</a><small class="wp_rp_comments_count"> (3)</small><br /></li><li data-position="9" data-poid="in-6597" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/11/golang-gofight-support-echo-framework/" class="wp_rp_title">輕量級 Gofight 支援 Echo 框架測試</a><small class="wp_rp_comments_count"> (0)</small><br /></li></ul></div></div>
]]></content:encoded>
			<wfw:commentRss>https://blog.wu-boy.com/2021/05/graceful-shutdown-with-progress-bar-in-golang/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MongoDB 效能調校紀錄</title>
		<link>https://blog.wu-boy.com/2021/05/mongodb-performance-tunning/</link>
		<comments>https://blog.wu-boy.com/2021/05/mongodb-performance-tunning/#respond</comments>
		<pubDate>Sun, 16 May 2021 00:57:45 +0000</pubDate>
		<dc:creator><![CDATA[appleboy]]></dc:creator>
				<category><![CDATA[sql]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[grafana]]></category>
		<category><![CDATA[mongodb]]></category>
		<category><![CDATA[prometheus]]></category>

		<guid isPermaLink="false">https://blog.wu-boy.com/?p=7936</guid>
		<description><![CDATA[最近剛好在實作 Prometheus + Grafana 的時候，對 MongoDB 做了容器 CPU 使用率 (container_cpu_usage_seconds_total) 的監控，Metrics 寫法如下: sum( rate(container_cpu_usage_seconds_total{name!~&#34;(^$&#124;^0_.*)&#34;}[1m])) by (name) 從上面的 Metrics 可以拉長時間來看，會發現專案的 MongoDB 非常不穩定，起起伏伏，這時候就需要來看看資料庫到底哪邊慢，以及看看哪個語法造成 CPU 飆高？ 接著為了看 MongoDB 的 Log 紀錄，把 Grafana 推出的 Loki，也導入專案系統，將容器所有的 Log 都導向 Loki，底下可以看看 docker-compose 將 Log 輸出到 loki logging: driver: loki options: loki-url: &#34;http://xxxxxxx/loki/api/v1/push&#34; loki-retries: &#34;5&#34; loki-batch-size: &#34;400&#34; loki-external-labels: &#34;environment=production,project=mongo&#34; 先看看結論，做法其實很簡單，找出相對應 Slow Query，把相關的欄位加上 Index，就可以解決了 &#8230; <a href="https://blog.wu-boy.com/2021/05/mongodb-performance-tunning/" class="more-link">Continue reading<span class="screen-reader-text"> "MongoDB 效能調校紀錄"</span></a>]]></description>
				<content:encoded><![CDATA[<p><a href="https://lh3.googleusercontent.com/DZKO3gMs5RhQ0-uGU2Y-uaTsb7HKCJU3lH91uggni5HA-fpDMqgvKPwHRwuo-jlCbAJZYyY9TKKovtCDT7OFgiclb2VYz58HwmDeHUX6FjlwfnuTkaTZxYudTIiuJ6yWsuNu2vs1vTQ=w1920-h1080" title="mongodb"><img src="https://lh3.googleusercontent.com/DZKO3gMs5RhQ0-uGU2Y-uaTsb7HKCJU3lH91uggni5HA-fpDMqgvKPwHRwuo-jlCbAJZYyY9TKKovtCDT7OFgiclb2VYz58HwmDeHUX6FjlwfnuTkaTZxYudTIiuJ6yWsuNu2vs1vTQ=w1920-h1080" alt="mongodb" title="mongodb" /></a></p>
<p>最近剛好在實作 <a href="https://prometheus.io/">Prometheus</a> + <a href="https://grafana.com/">Grafana</a> 的時候，對 MongoDB 做了容器 CPU 使用率 (<code>container_cpu_usage_seconds_total</code>) 的監控，Metrics 寫法如下:</p>
<pre><code class="language-bash">sum(
    rate(container_cpu_usage_seconds_total{name!~&quot;(^$|^0_.*)&quot;}[1m]))
by (name)</code></pre>
<p>從上面的 Metrics 可以拉長時間來看，會發現專案的 MongoDB 非常不穩定，起起伏伏，這時候就需要來看看資料庫到底哪邊慢，以及看看哪個語法造成 CPU 飆高？</p>
<p><img src="https://lh3.googleusercontent.com/FbcbJ75SVSEmNb94X6z9JsjkhKmuzjEGUesnTVxwcP2SGYWJpblQaD5ks02brR9kP9HYqP7KpbQAaoa7RUuBWi8EnXdN2eTCzekyGVmKAY4ltnmEnNrWerAzZkHIp9gGKieO71WUhJk=w1920-h1080" alt="" /></p>
<p>接著為了看 MongoDB 的 Log 紀錄，把 Grafana 推出的 <a href="https://grafana.com/oss/loki/">Loki</a>，也導入專案系統，將容器所有的 Log 都導向 Loki，底下可以看看 docker-compose 將 Log 輸出到 loki</p>
<pre><code class="language-yaml=">    logging:
      driver: loki
      options:
        loki-url: &quot;http://xxxxxxx/loki/api/v1/push&quot;
        loki-retries: &quot;5&quot;
        loki-batch-size: &quot;400&quot;
        loki-external-labels: &quot;environment=production,project=mongo&quot;</code></pre>
<p><img src="https://lh3.googleusercontent.com/TooE3Q49jzI_FpbjXm4b3A_9aho8J4Qws64XmhVzDVbe6NPMCmgYmuw5bMRwnMgmk_lXxNHDU1n6RXwFGvoZvPxLuM6clRJ_ZGRC9S47rvFbm3k9v6v8qaHhC6vqsFkXENQYlRAqKn0=w1920-h1080" alt="" /></p>
<p>先看看結論，做法其實很簡單，找出相對應 Slow Query，把相關的欄位加上 Index，就可以解決了</p>
<p><img src="https://lh3.googleusercontent.com/WgyOX8OOff6KGKlOvSvoNxzDsPGyiXBwPa_PX3O7L9AYSBfQ9VoNAP5s_HkbmMa7rokTnF--ZLnJ4p6oLqoTCV2Gyq7B696SEbTIrGIi1kDVtijVpZlYTklq3qbLFtpKGAQHeXxHE-Q=w1920-h1080" alt="" /></p>
<p><img src="https://lh3.googleusercontent.com/nzKkL7J5x_LmoqTFYav5kKA8Jkp4E4s8OCOekd-fz2HAeU2ySC3DopumqMIevqelMN_bvw7Ug7BB2f6ZJeubCQzz4w1Uby709NqsqTEkQcJK7IwVkcHt_ZkArRjSlKfZvyWBE6ZBLnY=w1920-h1080" alt="" /></p>
<span id="more-7936"></span>
<h2>啟動資料庫 Profiler</h2>
<p>MongoDB 預設 Profiler 是關閉的，遇到效能問題，就需要打開，來收集所有的操作記錄 (CRUD)，透過底下指令可以知道目前 MongoDB 的 <a href="https://docs.mongodb.com/manual/reference/command/profile/">Profiler 狀態</a></p>
<pre><code class="language-bash=">> db.getProfilingStatus()
{ &quot;was&quot; : 0, &quot;slowms&quot; : 100, &quot;sampleRate&quot; : 1 }</code></pre>
<p>可以看到 <code>was</code> 為 0 代表沒有啟動</p>
<table>
<thead>
<tr>
<th>Level</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>The profiler is off and does not collect any data. This is the default profiler level.</td>
</tr>
<tr>
<td>1</td>
<td>The profiler collects data for operations that take longer than the value of slowms.</td>
</tr>
<tr>
<td>2</td>
<td>The profiler collects data for all operations.</td>
</tr>
</tbody>
</table>
<p>這邊先將 Level 設定為 2，或者是只需要看 slow query，那就設定為 1</p>
<pre><code class="language-bash=">> db.setProfilingLevel(2)
{ &quot;was&quot; : 0, &quot;slowms&quot; : 100, &quot;sampleRate&quot; : 1, &quot;ok&quot; : 1 }</code></pre>
<p>如果使用完畢，請將 Profiler 關閉。</p>
<h2>用 Profiler 分析效能</h2>
<p>上一個步驟，Profile 打開後，就可以看到 Mongo 收集一堆 Slow Query Log 了</p>
<p><img src="https://lh3.googleusercontent.com/TooE3Q49jzI_FpbjXm4b3A_9aho8J4Qws64XmhVzDVbe6NPMCmgYmuw5bMRwnMgmk_lXxNHDU1n6RXwFGvoZvPxLuM6clRJ_ZGRC9S47rvFbm3k9v6v8qaHhC6vqsFkXENQYlRAqKn0=w1920-h1080" alt="" /></p>
<p>最後驗證結果就很簡單，只要 Log 量減少及 CPU 使用率下降，就代表成功了，底下介紹幾個好用的分析效能語法。第一直接找目前系統 command 類別內執行時間最久的狀況 (millis: -1 反向排序) </p>
<pre><code class="language-bash">db.system.profile.
  find({ op: { $eq: &quot;command&quot; }}).
  sort({ millis: -1 }).
  limit(2).
  pretty();</code></pre>
<p>第二可以找執行時間超過 100 ms 的指令。</p>
<pre><code class="language-bash">db.system.profile.
  find({ millis: { $gt: 100 }}).
  pretty();</code></pre>
<p>最後透過 <code>planSummary</code> 語法可以找出 query command 掃描 (<code>COLSCAN</code>) 整個資料表，代表語法沒有被優化，資料表越大，查詢速度越慢</p>
<pre><code class="language-bash=">db.system.profile.
  find({ &quot;planSummary&quot;: { $eq: &quot;COLLSCAN&quot; }, &quot;op&quot;: { $eq: &quot;query&quot; }}).
  sort({ millis: -1 }).
  pretty();</code></pre>
<p>或者可以透過 <a href="https://docs.mongodb.com/manual/reference/command/profile/#mongodb-dbcommand-dbcmd.profile">db.currentOp</a> 觀察現在正在執行中的 Command，底下語法可以針對 <code>db1</code> 資料庫查詢執行超過 3 秒鐘的指令</p>
<pre><code class="language-bash=">db.currentOp(
   {
     &quot;active&quot; : true,
     &quot;secs_running&quot; : { &quot;$gt&quot; : 3 },
     &quot;ns&quot; : /^db1\./
   }
)</code></pre>
<h2>了解 Slow Query</h2>
<p>從上面的 Profiler 效能分析指令，可以查詢到哪些 SQL 指令造成系統效能不穩定，這些 SQL 可以透過 <code>EXPLAIN</code> 方式找尋到執行效能瓶頸。底下直接透過 explain 方式會產生出 JSON 格式輸出：</p>
<pre><code class="language-shell=">db.orders.explain(&quot;executionStats&quot;).find({maID:&quot;bfce30cab12311eba55d09972&quot;,maOrderID:&quot;2222318209&quot;,deleted:false})</code></pre>
<p>透過 <a href="https://docs.mongodb.com/manual/reference/command/profile/#mongodb-dbcommand-dbcmd.profile">db.collection.explain</a> 可以知道此 Query 在 Mongodb 內是怎麼有效率的執行，底下來看看 <a href="https://docs.mongodb.com/manual/reference/explain-results/">explain</a> 回傳的結果:</p>
<pre><code class="language-json=">{
  &quot;queryPlanner&quot; : {
    &quot;plannerVersion&quot; : 1,
    &quot;namespace&quot; : &quot;fullinn.orders&quot;,
    &quot;indexFilterSet&quot; : false,
    &quot;parsedQuery&quot; : {
      &quot;$and&quot; : [
        {
          &quot;deleted&quot; : {
            &quot;$eq&quot; : false
          }
        },
        {
          &quot;maID&quot; : {
            &quot;$eq&quot; : &quot;bfce30cab12311eba55d09972&quot;
          }
        },
        {
          &quot;maOrderID&quot; : {
            &quot;$eq&quot; : &quot;2222318209&quot;
          }
        }
      ]
    },
    &quot;winningPlan&quot; : {
      &quot;stage&quot; : &quot;COLLSCAN&quot;,
      &quot;filter&quot; : {
        &quot;$and&quot; : [
          {
            &quot;deleted&quot; : {
              &quot;$eq&quot; : false
            }
          },
          {
            &quot;maID&quot; : {
              &quot;$eq&quot; : &quot;bfce30cab12311eba55d09972&quot;
            }
          },
          {
            &quot;maOrderID&quot; : {
              &quot;$eq&quot; : &quot;2222318209&quot;
            }
          }
        ]
      },
      &quot;direction&quot; : &quot;forward&quot;
    },
    &quot;rejectedPlans&quot; : [ ]
  },
  &quot;executionStats&quot; : {
    &quot;executionSuccess&quot; : true,
    &quot;nReturned&quot; : 0,
    &quot;executionTimeMillis&quot; : 237,
    &quot;totalKeysExamined&quot; : 0,
    &quot;totalDocsExamined&quot; : 192421,
    &quot;executionStages&quot; : {
      &quot;stage&quot; : &quot;COLLSCAN&quot;,
      &quot;filter&quot; : {
        &quot;$and&quot; : [
          {
            &quot;deleted&quot; : {
              &quot;$eq&quot; : false
            }
          },
          {
            &quot;maID&quot; : {
              &quot;$eq&quot; : &quot;bfce30cab12311eba55d09972&quot;
            }
          },
          {
            &quot;maOrderID&quot; : {
              &quot;$eq&quot; : &quot;2222318209&quot;
            }
          }
        ]
      },
      &quot;nReturned&quot; : 0,
      &quot;executionTimeMillisEstimate&quot; : 30,
      &quot;works&quot; : 192423,
      &quot;advanced&quot; : 0,
      &quot;needTime&quot; : 192422,
      &quot;needYield&quot; : 0,
      &quot;saveState&quot; : 192,
      &quot;restoreState&quot; : 192,
      &quot;isEOF&quot; : 1,
      &quot;direction&quot; : &quot;forward&quot;,
      &quot;docsExamined&quot; : 192421
    }
  },
  &quot;serverInfo&quot; : {
    &quot;host&quot; : &quot;60b424d18015&quot;,
    &quot;port&quot; : 27017,
    &quot;version&quot; : &quot;4.4.4&quot;,
    &quot;gitVersion&quot; : &quot;8db30a63db1a9d84bdcad0c83369623f708e0397&quot;
  },
  &quot;ok&quot; : 1
}</code></pre>
<p>直接注意到幾個數據，看到 <code>executionTimeMillis</code> 執行時間，<code>totalDocsExamined</code> 是在執行過程會掃過多少資料 (越低越好)，由上面可以知道此 Query 執行時間是 <code>237 ms</code>，並且需要掃過 <code>192421</code> 筆資料，另外一個重要指標就是 <code>executionStages</code> 內的 <code>stage</code></p>
<pre><code class="language-json=">    &quot;executionStages&quot; : {
      &quot;stage&quot; : &quot;COLLSCAN&quot;,
      &quot;filter&quot; : {
        &quot;$and&quot; : [
          {
            &quot;deleted&quot; : {
              &quot;$eq&quot; : false
            }
          },
          {
            &quot;maID&quot; : {
              &quot;$eq&quot; : &quot;bfce30cab12311eba55d09972&quot;
            }
          },
          {
            &quot;maOrderID&quot; : {
              &quot;$eq&quot; : &quot;2222318209&quot;
            }
          }
        ]
      },
      &quot;nReturned&quot; : 0,
      &quot;executionTimeMillisEstimate&quot; : 30,
      &quot;works&quot; : 192423,
      &quot;advanced&quot; : 0,
      &quot;needTime&quot; : 192422,
      &quot;needYield&quot; : 0,
      &quot;saveState&quot; : 192,
      &quot;restoreState&quot; : 192,
      &quot;isEOF&quot; : 1,
      &quot;direction&quot; : &quot;forward&quot;,
      &quot;docsExamined&quot; : 192421
    }
  },</code></pre>
<p>Stage 狀態分成底下幾種</p>
<ul>
<li><strong>COLLSCAN</strong>: for a collection scan</li>
<li><strong>IXSCAN</strong>: for scanning index keys</li>
<li><strong>FETCH</strong>: for retrieving documents</li>
<li><strong>SHARD_MERGE</strong>: for merging results from shards</li>
<li><strong>SHARDING_FILTER</strong>: for filtering out orphan documents from shards</li>
</ul>
<p>這次我們遇到的就是第一種 <code>COLLSCAN</code>，資料表全掃，所以造成效能非常低，這時就要檢查看看是否哪邊增加 Index 可以解決效能問題。底下增加一個 index key 看看結果如何？</p>
<pre><code class="language-shell=">db.orders.createIndex({maID: 1})</code></pre>
<p>接著再執行一次，可以看到底下結果:</p>
<pre><code class="language-json=">  &quot;executionStats&quot; : {
    &quot;executionSuccess&quot; : true,
    &quot;nReturned&quot; : 0,
    &quot;executionTimeMillis&quot; : 2,
    &quot;totalKeysExamined&quot; : 1,
    &quot;totalDocsExamined&quot; : 1,
    &quot;executionStages&quot; : {
      &quot;stage&quot; : &quot;FETCH&quot;,
      &quot;filter&quot; : {
        &quot;$and&quot; : [
          {
            &quot;deleted&quot; : {
              &quot;$eq&quot; : false
            }
          },
          {
            &quot;maOrderID&quot; : {
              &quot;$eq&quot; : &quot;2222318209&quot;
            }
          }
        ]
      },
      &quot;nReturned&quot; : 0,
      &quot;executionTimeMillisEstimate&quot; : 0,
      &quot;works&quot; : 3,
      &quot;advanced&quot; : 0,
      &quot;needTime&quot; : 1,
      &quot;needYield&quot; : 0,
      &quot;saveState&quot; : 0,
      &quot;restoreState&quot; : 0,
      &quot;isEOF&quot; : 1,
      &quot;docsExamined&quot; : 1,
      &quot;alreadyHasObj&quot; : 0,
      &quot;inputStage&quot; : {
        &quot;stage&quot; : &quot;IXSCAN&quot;,
        &quot;nReturned&quot; : 1,
        &quot;executionTimeMillisEstimate&quot; : 0,
        &quot;works&quot; : 2,
        &quot;advanced&quot; : 1,
        &quot;needTime&quot; : 0,
        &quot;needYield&quot; : 0,
        &quot;saveState&quot; : 0,
        &quot;restoreState&quot; : 0,
        &quot;isEOF&quot; : 1,
        &quot;keyPattern&quot; : {
          &quot;maID&quot; : 1
        },
        &quot;indexName&quot; : &quot;maID_1&quot;,
        &quot;isMultiKey&quot; : false,
        &quot;multiKeyPaths&quot; : {
          &quot;maID&quot; : [ ]
        },
        &quot;isUnique&quot; : false,
        &quot;isSparse&quot; : false,
        &quot;isPartial&quot; : false,
        &quot;indexVersion&quot; : 2,
        &quot;direction&quot; : &quot;forward&quot;,
        &quot;indexBounds&quot; : {
          &quot;maID&quot; : [
            &quot;[\&quot;bfce30cab12311eba55d09972\&quot;, \&quot;bfce30cab12311eba55d09972\&quot;]&quot;
          ]
        },
        &quot;keysExamined&quot; : 1,
        &quot;seeks&quot; : 1,
        &quot;dupsTested&quot; : 0,
        &quot;dupsDropped&quot; : 0
      }
    }
  },</code></pre>
<p>可以看到 <code>executionTimeMillis</code> 降低到 2，<code>totalDocsExamined</code> 變成 1，用 index 去找就是特別快。inputStage.stage 用的就是 <code>IXSCAN</code>。針對上述找尋方式把相對的 index key 補上，並且優化商業邏輯，就可以達到底下結果</p>
<p><img src="https://lh3.googleusercontent.com/nzKkL7J5x_LmoqTFYav5kKA8Jkp4E4s8OCOekd-fz2HAeU2ySC3DopumqMIevqelMN_bvw7Ug7BB2f6ZJeubCQzz4w1Uby709NqsqTEkQcJK7IwVkcHt_ZkArRjSlKfZvyWBE6ZBLnY=w1920-h1080" alt="" /></p>
<p>相關參考文件:</p>
<ul>
<li><a href="https://docs.mongodb.com/manual/core/index-single/">Single Field Indexes</a></li>
<li><a href="https://docs.mongodb.com/manual/reference/database-profiler/">Database Profiler Output</a></li>
<li><a href="https://docs.mongodb.com/manual/tutorial/manage-the-database-profiler/">Database Profiler</a></li>
</ul>
<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">Related View</h3><ul class="related_post wp_rp"><li data-position="0" data-poid="in-7076" data-post-type="none" ><a href="https://blog.wu-boy.com/2018/09/converting-timestamp-to-timestamp-in-a-specific-time-zone-in-postgres/" class="wp_rp_title">在 PostgreSQL 時區轉換及計算時間</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="1" data-poid="in-6966" data-post-type="none" ><a href="https://blog.wu-boy.com/2018/03/simple-queue-with-optimistic-concurrency-in-go/" class="wp_rp_title">用 Go 語言實現單一或多重 Queue 搭配 optimistic concurrency</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="2" data-poid="in-7753" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/06/convert-postgres-data-to-csv-file/" class="wp_rp_title">將 Postgres 資料轉換到 CSV 格式</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="3" data-poid="in-7829" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/12/docker-image-to-periodically-backup-your-database-mysql-postgres-or-mongodb-to-s3/" class="wp_rp_title">用 Docker 每天自動化備份 MySQL, Postgres 或 MongoDB 並上傳到 AWS S3</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="4" data-poid="in-4779" data-post-type="none" ><a href="https://blog.wu-boy.com/2013/12/increase-phpmyadmin-login-time/" class="wp_rp_title">增加 phpMyAdmin 登入時間</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="5" data-poid="in-7593" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/02/how-to-select-top-n-rows-from-each-key-in-sql/" class="wp_rp_title">[SQL] 如何從單一資料表取得每個 key 前 n 筆資料</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="6" data-poid="in-1504" data-post-type="none" ><a href="https://blog.wu-boy.com/2009/07/sql-mysql-row_number-simulation/" class="wp_rp_title">[SQL] MySQL ROW_NUMBER Simulation </a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="7" data-poid="in-137" data-post-type="none" ><a href="https://blog.wu-boy.com/2008/01/mysql-outer-join-%e4%bd%bf%e7%94%a8/" class="wp_rp_title">[MySQL] outer join 使用</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="8" data-poid="in-133" data-post-type="none" ><a href="https://blog.wu-boy.com/2008/01/bcb-%e5%a6%82%e4%bd%95%e5%88%a9%e7%94%a8%e9%80%a3%e7%b5%90-mysql-5%e4%bb%a5%e4%b8%8a%e8%b7%9f-mssql-server/" class="wp_rp_title">[BCB] 如何利用連結 MySQL 5以上跟 MSSQL Server</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="9" data-poid="in-7925" data-post-type="none" ><a href="https://blog.wu-boy.com/2021/05/comunicate-with-open-policy-agent-using-resful-api/" class="wp_rp_title">使用 RESTful API 串接 Open Policy Agent</a><small class="wp_rp_comments_count"> (0)</small><br /></li></ul></div></div>
]]></content:encoded>
			<wfw:commentRss>https://blog.wu-boy.com/2021/05/mongodb-performance-tunning/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用 RESTful API 串接 Open Policy Agent</title>
		<link>https://blog.wu-boy.com/2021/05/comunicate-with-open-policy-agent-using-resful-api/</link>
		<comments>https://blog.wu-boy.com/2021/05/comunicate-with-open-policy-agent-using-resful-api/#respond</comments>
		<pubDate>Tue, 04 May 2021 02:14:12 +0000</pubDate>
		<dc:creator><![CDATA[appleboy]]></dc:creator>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[open policy agent]]></category>

		<guid isPermaLink="false">https://blog.wu-boy.com/?p=7925</guid>
		<description><![CDATA[上一篇『初探 Open Policy Agent 實作 RBAC (Role-based access control) 權限控管』介紹了如何透過 Go 語言直接嵌入 Open Policy Agent (簡稱 OPA)設定檔，並透過 Go 套件直接查詢使用者權限。由於目前 OPA 只有支援三種模式串接各種不同的 Application，一種是透過 Go 語言直接整合，詳細請看上一篇教學，另一種是透過 RESTful API，也就是本篇的教學，最後一種是透過 WebAssembly 讓其他 application 可以直接讀取。之後有機會再來寫 WebAssembly 教學。而本篇將帶您了解如何透過 RESTful API 方式來完成 RBAC 權限控管，其實我比較期待支援 gRPC 模式，但是看到這篇 issue 提到，OPA 現在已經支援 Plugin 模式，大家想擴充的，可以自行處理。 請求流程 可以看到上述圖片看到整個 OPA 系統流程，OPA 就是確保各種 API Request 的權限，首先第一步驟會帶著 Auuthorization Token 去跟單一個服務詢問，接著此服務就會將資料帶到 &#8230; <a href="https://blog.wu-boy.com/2021/05/comunicate-with-open-policy-agent-using-resful-api/" class="more-link">Continue reading<span class="screen-reader-text"> "使用 RESTful API 串接 Open Policy Agent"</span></a>]]></description>
				<content:encoded><![CDATA[<p><a href="https://lh3.googleusercontent.com/qLGheyjm3eVL-TRP_MT1X9j2QrNrtIIAlVPmLbvNGWcLkqfUTpH87D2GCzYmce8eU88oMF-82lSqT6DwOByPWEKVZP4nGWT-IZFDvpVwnil2AeXZaYxZN5J33IpfsYfP6mljV3S51R4=w1920-h1080" title="Open Policy Agent"><img src="https://lh3.googleusercontent.com/qLGheyjm3eVL-TRP_MT1X9j2QrNrtIIAlVPmLbvNGWcLkqfUTpH87D2GCzYmce8eU88oMF-82lSqT6DwOByPWEKVZP4nGWT-IZFDvpVwnil2AeXZaYxZN5J33IpfsYfP6mljV3S51R4=w1920-h1080" alt="Open Policy Agent" title="Open Policy Agent" /></a></p>
<p>上一篇『<a href="https://blog.wu-boy.com/2021/04/setup-rbac-role-based-access-control-using-open-policy-agent/">初探 Open Policy Agent 實作 RBAC (Role-based access control) 權限控管</a>』介紹了如何透過 <a href="https://golang.org">Go 語言</a>直接嵌入 Open Policy Agent (簡稱 OPA)設定檔，並透過 Go 套件直接查詢使用者權限。由於目前 OPA 只有支援三種模式串接各種不同的 Application，一種是透過 Go 語言直接整合，詳細請看上一篇教學，另一種是透過 RESTful API，也就是本篇的教學，最後一種是透過 <a href="https://webassembly.org/">WebAssembly</a> 讓其他 application 可以直接讀取。之後有機會再來寫 WebAssembly 教學。而本篇將帶您了解如何透過 RESTful API 方式來完成 <a href="https://en.wikipedia.org/wiki/Role-based_access_control">RBAC 權限控管</a>，其實我比較期待支援 <a href="https://grpc.io/">gRPC</a> 模式，但是看到這篇 <a href="https://github.com/open-policy-agent/opa/issues/841">issue 提到</a>，OPA 現在已經支援 Plugin 模式，大家想擴充的，可以自行處理。</p>
<span id="more-7925"></span>
<h2>請求流程</h2>
<p><a href="https://lh3.googleusercontent.com/Sjqfv-XLOfdlK2GmoZdmcEM0UYKzNsKSas3xh1GfBc4UIQLKjmqjbC_ULUgM_2Jf76yI6baveAn_uAfJ5DGPbo39zRhKgvf7pOGsWSi1B4wW4DvciTrCuL2O5J1n-YUF43F3DqFOPQM=w1920-h1080" title="blog_01"><img src="https://lh3.googleusercontent.com/Sjqfv-XLOfdlK2GmoZdmcEM0UYKzNsKSas3xh1GfBc4UIQLKjmqjbC_ULUgM_2Jf76yI6baveAn_uAfJ5DGPbo39zRhKgvf7pOGsWSi1B4wW4DvciTrCuL2O5J1n-YUF43F3DqFOPQM=w1920-h1080" alt="blog_01" title="blog_01" /></a></p>
<p>可以看到上述圖片看到整個 OPA 系統流程，OPA 就是確保各種 API Request 的權限，首先第一步驟會帶著 Auuthorization Token 去跟單一個服務詢問，接著此服務就會將資料帶到 OPA 進行查詢此 請求是否有權限可以存取，最後 OPA 會回傳結果，接著再由服務端決定要回送哪些訊息。那本篇的重點會在下圖部分</p>
<p><a href="https://lh3.googleusercontent.com/yjVOXbiFYt6XZ577GOqJLCh5cY-kPxEyxuSIo_7TbkQBmhSn0zdW5RmxwmNLumP5QJBxfuKlUTZWQchqFzVKRVtyxefwzxmkZou5ck_06guA7eVXViEW_mtnfNG3YvKqwNsXrxcCLlE=w1920-h1080" title="blog_02"><img src="https://lh3.googleusercontent.com/yjVOXbiFYt6XZ577GOqJLCh5cY-kPxEyxuSIo_7TbkQBmhSn0zdW5RmxwmNLumP5QJBxfuKlUTZWQchqFzVKRVtyxefwzxmkZou5ck_06guA7eVXViEW_mtnfNG3YvKqwNsXrxcCLlE=w1920-h1080" alt="blog_02" title="blog_02" /></a></p>
<h2>準備查詢資料</h2>
<p>底下是 OPA 的系統流程圖</p>
<p><a href="https://lh3.googleusercontent.com/wAdNLaVqbVK9JoP124HisUautnVurHNn8QTVvgdbfKsI_zv4OXVNzFrQLs4n5xtpbSPK_khXbV2AmXzc197GGfHFdsHbbM8O9Gj9O48LVJhF-fEInUHhdcGPfUjEHNSn4Ygvq50FqTY=w1920-h1080" title="blog_03"><img src="https://lh3.googleusercontent.com/wAdNLaVqbVK9JoP124HisUautnVurHNn8QTVvgdbfKsI_zv4OXVNzFrQLs4n5xtpbSPK_khXbV2AmXzc197GGfHFdsHbbM8O9Gj9O48LVJhF-fEInUHhdcGPfUjEHNSn4Ygvq50FqTY=w1920-h1080" alt="blog_03" title="blog_03" /></a></p>
<p>我們可以很清楚知道，要拿到最後的 Query Result (JSON) 需要有三個 Input 的，分別是</p>
<ol>
<li>Query Input (JSON)</li>
<li>Data (JSON)</li>
<li>Policy (Rego)</li>
</ol>
<p>就以 RBAC 為範例，第一個 <strong>Query Input</strong> 的資料，就需要帶入像是使用者所在的群組 (user)，使用者現在要執行的動作 (action)，使用者要對什麼資源做事情 (object)。這三個資料轉成 JSON 格式如下:</p>
<pre><code class="language-json">{
  &quot;input&quot;: {
    &quot;user&quot;: [&quot;design_group_kpi_editor&quot;, &quot;system_group_kpi_editor&quot;],
    &quot;action&quot;: &quot;edit&quot;,
    &quot;object&quot;: &quot;design&quot;
  }
}</code></pre>
<p>第二項就是準備系統內建的 Data 資料給 OPA，上述資料可以看到 user 所在的群組資訊，但是這些群組能做哪些事情，是 OPA 沒辦法知道的，所以需要將這些資料整理成 JSON 格式，並且上傳到 OPA 系統內</p>
<pre><code class="language-json">{
  &quot;group_roles&quot;: {
    &quot;admin&quot;: [&quot;admin&quot;],
    &quot;quality_head_design&quot;: [&quot;quality_head_design&quot;],
    &quot;quality_head_system&quot;: [&quot;quality_head_system&quot;],
    &quot;quality_head_manufacture&quot;: [&quot;quality_head_manufacture&quot;],
    &quot;kpi_editor_design&quot;: [&quot;kpi_editor_design&quot;],
    &quot;kpi_editor_system&quot;: [&quot;kpi_editor_system&quot;],
    &quot;kpi_editor_manufacture&quot;: [&quot;kpi_editor_manufacture&quot;],
    &quot;viewer&quot;: [&quot;viewer&quot;],
    &quot;viewer_limit_ds&quot;: [&quot;viewer_limit_ds&quot;],
    &quot;viewer_limit_m&quot;: [&quot;viewer_limit_m&quot;],
    &quot;design_group_kpi_editor&quot;: [&quot;kpi_editor_design&quot;, &quot;viewer_limit_ds&quot;],
    &quot;system_group_kpi_editor&quot;: [&quot;kpi_editor_system&quot;, &quot;viewer_limit_ds&quot;],
    &quot;manufacture_group_kpi_editor&quot;: [&quot;kpi_editor_manufacture&quot;, &quot;viewer&quot;],
    &quot;project_leader&quot;: [&quot;viewer_limit_ds&quot;, &quot;viewer_limit_m&quot;]
  },
  &quot;role_permissions&quot;: {
    &quot;admin&quot;: [
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;design&quot;},
      {&quot;action&quot;: &quot;edit&quot;, &quot;object&quot;: &quot;design&quot;},
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;system&quot;},
      {&quot;action&quot;: &quot;edit&quot;, &quot;object&quot;: &quot;system&quot;},
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;manufacture&quot;},
      {&quot;action&quot;: &quot;edit&quot;, &quot;object&quot;: &quot;manufacture&quot;}
    ],
    &quot;quality_head_design&quot;: [
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;design&quot;},
      {&quot;action&quot;: &quot;edit&quot;, &quot;object&quot;: &quot;design&quot;},
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;system&quot;},
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;manufacture&quot;}
    ],
    &quot;quality_head_system&quot;: [
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;design&quot;},
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;system&quot;},
      {&quot;action&quot;: &quot;edit&quot;, &quot;object&quot;: &quot;system&quot;},
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;manufacture&quot;}
    ],
    &quot;quality_head_manufacture&quot;: [
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;design&quot;},
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;system&quot;},
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;manufacture&quot;},
      {&quot;action&quot;: &quot;edit&quot;, &quot;object&quot;: &quot;manufacture&quot;}
    ],
    &quot;kpi_editor_design&quot;: [
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;design&quot;},
      {&quot;action&quot;: &quot;edit&quot;, &quot;object&quot;: &quot;design&quot;}
    ],
    &quot;kpi_editor_system&quot;: [
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;system&quot;},
      {&quot;action&quot;: &quot;edit&quot;, &quot;object&quot;: &quot;system&quot;}
    ],
    &quot;kpi_editor_manufacture&quot;: [
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;manufacture&quot;},
      {&quot;action&quot;: &quot;edit&quot;, &quot;object&quot;: &quot;manufacture&quot;}
    ],
    &quot;viewer&quot;: [
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;design&quot;},
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;system&quot;},
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;manufacture&quot;}
    ],
    &quot;viewer_limit_ds&quot;: [
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;design&quot;},
      {&quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;system&quot;}
    ],
    &quot;viewer_limit_m&quot;: [{&quot;action&quot;: &quot;view_l3_project&quot;, &quot;object&quot;: &quot;manufacture&quot;}]
  }
}</code></pre>
<p>上述 Data 可以知道 Group 跟 Role 的對應關係，以及 Role 可以做得相對應事情。最後一項就是撰寫 OPA Policy，要透過 <a href="https://www.openpolicyagent.org/docs/latest/policy-language/">Rego 語言</a>來撰寫，其實沒有很難。</p>
<pre><code class="language-go">package rbac.authz

import data.rbac.authz.acl
import input

# logic that implements RBAC.
default allow = false

allow {
    # lookup the list of roles for the user
    roles := acl.group_roles[input.user[_]]

    # for each role in that list
    r := roles[_]

    # lookup the permissions list for role r
    permissions := acl.role_permissions[r]

    # for each permission
    p := permissions[_]

    # check if the permission granted to r matches the user&#039;s request
    p == {&quot;action&quot;: input.action, &quot;object&quot;: input.object}
}</code></pre>
<p>上面就是先把 User 對應的 Group Role 找到之後，再將全部的 Role 權限拿出來進行最後的比對產生結果，回傳值就會是 <code>allow</code> 布林值。</p>
<h2>RESTful API</h2>
<p>上述步驟將檔案整理成底下三個</p>
<ol>
<li>input.json</li>
<li>data.json</li>
<li>rbac.authz.rego</li>
</ol>
<p>透過底下指令依序將資料上傳到 OPA Server 內，第一個先上傳 data</p>
<pre><code class="language-bash">curl -X PUT http://localhost:8181/v1/data/rbac/authz/acl \
  --data-binary @data.json</code></pre>
<p>接著上傳 Policy</p>
<pre><code class="language-bash">curl -X PUT http://localhost:8181/v1/policies/rbac.authz \
  --data-binary @rbac.authz.rego</code></pre>
<p>最後驗證 input 資料</p>
<pre><code class="language-bash">curl -X POST http://localhost:8181/v1/data/rbac/authz/allow \
  --data-binary @input.json</code></pre>
<p>用 <a href="https://github.com/astaxie/bat">bat tool</a> 驗證</p>
<pre><code class="language-bash">$ bat POST http://localhost:8181/v1/data/rbac/authz/allow &lt; input.json
POST /v1/data/rbac/authz/allow HTTP/1.1
Host: localhost:8181
Accept: application/json
Accept-Encoding: gzip, deflate
Content-Type: application/json
User-Agent: bat/0.1.0

{&quot;input&quot;:{&quot;action&quot;:&quot;edit&quot;,&quot;object&quot;:&quot;design&quot;,&quot;user&quot;:[&quot;design_group_kpi_editor&quot;,&quot;system_group_kpi_editor&quot;]}}

HTTP/1.1 200 OK
Content-Type: application/json
Date: Sat, 01 May 2021 08:43:30 GMT
Content-Length: 15

{
  &quot;result&quot;: true
}</code></pre>
<p>或者可以透過簡單的 Go 語言來驗證，用 Go 1.16 新的 embed package 來驗證。</p>
<pre><code class="language-go">package main

import (
    &quot;bytes&quot;
    _ &quot;embed&quot;
    &quot;fmt&quot;
    &quot;io/ioutil&quot;
    &quot;net/http&quot;
    &quot;time&quot;
)

//go:embed input.json
var input []byte

func main() {
    url := &quot;http://localhost:8181/v1/data/rbac/authz/allow&quot;
    method := &quot;POST&quot;

    payload := bytes.NewReader(input)

    client := &amp;http.Client{
        Timeout: 5 * time.Second,
    }
    req, err := http.NewRequest(method, url, payload)

    if err != nil {
        fmt.Println(err)
        return
    }
    req.Header.Add(&quot;Content-Type&quot;, &quot;application/json&quot;)

    res, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer res.Body.Close()

    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(body))
}</code></pre>
<h2>結論</h2>
<p>本篇透過 RESTful 方式來更新 Data 及 Policy，此方式相對於前篇直接坎入在 Go 語言內，效率上來說會比較差些，因為會牽扯到你把 OPA 部署在哪個環境內，中間肯定會有一些 Latency，不過這就要看團隊怎麼使用 OPA 了，各有優缺點，團隊如果不是在寫 Go 語言的，肯定只能用 RESTful 方式來更新及查詢。程式碼可以在<a href="https://github.com/go-training/opa-restful">這邊找到</a>。</p>
<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">Related View</h3><ul class="related_post wp_rp"><li data-position="0" data-poid="in-7916" data-post-type="none" ><a href="https://blog.wu-boy.com/2021/04/setup-rbac-role-based-access-control-using-open-policy-agent/" class="wp_rp_title">初探 Open Policy Agent 實作 RBAC (Role-based access control) 權限控管</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="1" data-poid="in-7352" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/04/install-specific-go-version-in-appveyor/" class="wp_rp_title">在 appveyor 內指定 Go 語言編譯版本</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="2" data-poid="in-6198" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/04/gofight-tool-for-api-handler-testing-in-golang/" class="wp_rp_title">用 gofight 來測試 golang web API handler</a><small class="wp_rp_comments_count"> (3)</small><br /></li><li data-position="3" data-poid="in-6674" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/03/golang-dependency-management-tool-dep/" class="wp_rp_title">Go 語言官方推出的 dep 使用心得</a><small class="wp_rp_comments_count"> (6)</small><br /></li><li data-position="4" data-poid="in-6966" data-post-type="none" ><a href="https://blog.wu-boy.com/2018/03/simple-queue-with-optimistic-concurrency-in-go/" class="wp_rp_title">用 Go 語言實現單一或多重 Queue 搭配 optimistic concurrency</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="5" data-poid="in-7584" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/01/when-to-use-go-channel-and-goroutine/" class="wp_rp_title">使用 Go Channel 及 Goroutine 時機</a><small class="wp_rp_comments_count"> (2)</small><br /></li><li data-position="6" data-poid="in-6597" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/11/golang-gofight-support-echo-framework/" class="wp_rp_title">輕量級 Gofight 支援 Echo 框架測試</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="7" data-poid="in-6858" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/10/go-configuration-with-viper/" class="wp_rp_title">在 Go 語言使用 Viper 管理設定檔</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="8" data-poid="in-6869" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/11/gorush-a-push-notification-server-written-in-go/" class="wp_rp_title">Gorush 輕量級手機訊息發送服務</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="9" data-poid="in-6963" data-post-type="none" ><a href="https://blog.wu-boy.com/2018/02/simply-output-go-html-template-execution-to-strings/" class="wp_rp_title">將 Go Html Template 存入 String 變數</a><small class="wp_rp_comments_count"> (0)</small><br /></li></ul></div></div>
]]></content:encoded>
			<wfw:commentRss>https://blog.wu-boy.com/2021/05/comunicate-with-open-policy-agent-using-resful-api/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>初探 Open Policy Agent 實作 RBAC (Role-based access control) 權限控管</title>
		<link>https://blog.wu-boy.com/2021/04/setup-rbac-role-based-access-control-using-open-policy-agent/</link>
		<comments>https://blog.wu-boy.com/2021/04/setup-rbac-role-based-access-control-using-open-policy-agent/#comments</comments>
		<pubDate>Sun, 18 Apr 2021 07:24:13 +0000</pubDate>
		<dc:creator><![CDATA[appleboy]]></dc:creator>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[open policy agent]]></category>
		<category><![CDATA[RBAC]]></category>

		<guid isPermaLink="false">https://blog.wu-boy.com/?p=7916</guid>
		<description><![CDATA[最近公司內部多個專案都需要用到 RBAC (Role-based access control) 權限控管，所以決定來找尋 Go 語言的解決方案及套件，在 Go 語言比較常聽到的就是 Casbin，大家眾所皆知，但是隨著專案變大，系統複雜性更高，希望未來可以打造一套可擴充性的權限機制，故網路上看到一篇 ladon vs casbin 的介紹文章，文章留言有中國開發者對於 Casbin 的一些看法，以及最後他推薦另一套 CNCF 的專案叫 Open Policy Agent 來實作權限控管機制。本篇直接來針對 Open Policy Agent 簡稱 (OPA) 來做介紹，並且用 Go 語言來驗證 RBAC 權限。底下是文章內其他開發者用過 Casbin 的感想 1.使用覺得ladon的質量更好，支持類ACL和RBAC的權限系統，跟亞馬遜AWS的IAM非常契合 2.casbin那些庫的質量真的是無力吐槽，都沒有經常測試的東西就往github發，UI也到處bug，全都是畢業生寫的一樣，試用便知 3.casbin這個項目不讓提問題，提問題就給你關閉，作者很涉別人提問題 4.這些確實是本人的經歷，大家慎重選擇吧 最後的推薦 強烈推薦CNCF今年畢業的策略引擎OPA（維護團隊主要是Google，微軟，Styra等），可以實現ABAC，RBAC，PBAC等各種權限模型，目前我們已經在生產環境中使用。 也是基於OPA實現的。 本篇所使用的範例程式碼請從這邊下載或觀看。 線上影片 Go 語言基礎實戰 (開發, 測試及部署) 一天學會 DevOps 自動化測試及部署 DOCKER 容器開發部署實戰 如果需要搭配購買請直接透過 FB &#8230; <a href="https://blog.wu-boy.com/2021/04/setup-rbac-role-based-access-control-using-open-policy-agent/" class="more-link">Continue reading<span class="screen-reader-text"> "初探 Open Policy Agent 實作 RBAC (Role-based access control) 權限控管"</span></a>]]></description>
				<content:encoded><![CDATA[<p><a href="https://lh3.googleusercontent.com/qLGheyjm3eVL-TRP_MT1X9j2QrNrtIIAlVPmLbvNGWcLkqfUTpH87D2GCzYmce8eU88oMF-82lSqT6DwOByPWEKVZP4nGWT-IZFDvpVwnil2AeXZaYxZN5J33IpfsYfP6mljV3S51R4=w1920-h1080" title="Open Policy Agent"><img src="https://lh3.googleusercontent.com/qLGheyjm3eVL-TRP_MT1X9j2QrNrtIIAlVPmLbvNGWcLkqfUTpH87D2GCzYmce8eU88oMF-82lSqT6DwOByPWEKVZP4nGWT-IZFDvpVwnil2AeXZaYxZN5J33IpfsYfP6mljV3S51R4=w1920-h1080" alt="Open Policy Agent" title="Open Policy Agent" /></a></p>
<p>最近公司內部多個專案都需要用到 <a href="https://zh.wikipedia.org/wiki/%E4%BB%A5%E8%A7%92%E8%89%B2%E7%82%BA%E5%9F%BA%E7%A4%8E%E7%9A%84%E5%AD%98%E5%8F%96%E6%8E%A7%E5%88%B6">RBAC</a> (Role-based access control) 權限控管，所以決定來找尋 Go 語言的解決方案及套件，在 Go 語言比較常聽到的就是 <a href="https://casbin.org/">Casbin</a>，大家眾所皆知，但是隨著專案變大，系統複雜性更高，希望未來可以打造一套可擴充性的權限機制，故網路上看到一篇 <a href="https://gist.github.com/Wang-Kai/18fe4e662ef795805c14b1ec94932834">ladon vs casbin</a> 的介紹文章，文章留言有中國開發者對於 Casbin 的一些看法，以及最後他推薦另一套 <a href="https://www.cncf.io/">CNCF</a> 的專案叫 <a href="https://www.openpolicyagent.org/">Open Policy Agent</a> 來實作權限控管機制。本篇直接來針對 Open Policy Agent 簡稱 (OPA) 來做介紹，並且用 Go 語言來驗證 RBAC 權限。底下是文章內其他開發者用過 Casbin 的感想</p>
<blockquote>
<p>1.使用覺得ladon的質量更好，支持類ACL和RBAC的權限系統，跟亞馬遜AWS的IAM非常契合
2.casbin那些庫的質量真的是無力吐槽，都沒有經常測試的東西就往github發，UI也到處bug，全都是畢業生寫的一樣，試用便知
3.casbin這個項目不讓提問題，提問題就給你關閉，作者很涉別人提問題
4.這些確實是本人的經歷，大家慎重選擇吧</p>
</blockquote>
<p>最後的推薦</p>
<blockquote>
<p>強烈推薦CNCF今年畢業的策略引擎OPA（維護團隊主要是Google，微軟，Styra等），可以實現ABAC，RBAC，PBAC等各種權限模型，目前我們已經在生產環境中使用。 也是基於OPA實現的。</p>
</blockquote>
<p>本篇所使用的範例程式碼請從<a href="https://github.com/go-training/opa-demo/tree/v0.0.1">這邊下載或觀看</a>。</p>
<span id="more-7916"></span>
<h2>線上影片</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/AkMVh5XRcuI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<ul>
<li><a href="https://www.udemy.com/course/golang-fight/?couponCode=202104">Go 語言基礎實戰 (開發, 測試及部署)</a></li>
<li><a href="https://www.udemy.com/course/devops-oneday/?couponCode=202104">一天學會 DevOps 自動化測試及部署</a></li>
<li><a href="https://www.udemy.com/course/docker-practice/?couponCode=202104">DOCKER 容器開發部署實戰</a></li>
</ul>
<p>如果需要搭配購買請直接透過 <a href="http://facebook.com/appleboy46">FB 聯絡我</a>，直接匯款（價格再減 <strong>100</strong>）</p>
<h2>什麼是 Open Policy Agent</h2>
<p><a href="https://www.openpolicyagent.org/">Open Policy Agent</a> (念 &quot;oh-pa&quot;) 是一套開源專案，用來讓開發者制定各種不同的 Policy 機制，並且創造了 OPA’s policy 語言 (<a href="https://www.openpolicyagent.org/docs/latest/policy-language/">Rego</a>) 來協助開發者快速撰寫各種不同的 Policy 政策，並且可以透過 Command (opa) 來驗證及測試。透過 OPA 可以制定像是微服務或 CI/CD Pipeline 等之間溝通的政策，來達到權限的分離。底下用一張官網的圖來介紹</p>
<p><a href="https://lh3.googleusercontent.com/sP-328kBFP9pBLIrbkfl10LboSbkK4kx5fffXG0fUeo-3IMtQewwzGGKA-4a212Y9pHuZt_kgU7tYnXaDBWeUiPDUfdWoSKbxYW4xJOsCwVpHRvN7ssGeL2wojIpugquI4ef-IzblJM=w1920-h1080" title="open policy agent infra"><img src="https://lh3.googleusercontent.com/sP-328kBFP9pBLIrbkfl10LboSbkK4kx5fffXG0fUeo-3IMtQewwzGGKA-4a212Y9pHuZt_kgU7tYnXaDBWeUiPDUfdWoSKbxYW4xJOsCwVpHRvN7ssGeL2wojIpugquI4ef-IzblJM=w1920-h1080" alt="open policy agent infra" title="open policy agent infra" /></a></p>
<p>簡單來說各個服務之間有不同的權限需要處理，這時透過 OPA 專門做授權管理的服務會是最好的，整個流程就會如下:</p>
<ol>
<li>服務定義好 Query 格式 (任意的 JSON 格式)</li>
<li>撰寫所有授權政策 (Rego)</li>
<li>準備在授權過程需要用到的資料 (Data JSON)</li>
<li>OPA 執行決定，並回傳服務所需的資料 (任意的 JSON 格式)</li>
</ol>
<h2>撰寫 RBAC 政策及驗證</h2>
<p>OPA 官網已經<a href="https://www.openpolicyagent.org/docs/latest/comparison-to-other-systems/#role-based-access-control-rbac">提供完整的範例</a>給各位開發者參考，也有完整的 <a href="https://www.openpolicyagent.org/docs/latest/policy-language">Rego 文件格式</a>，我們先定義 User 跟 Role 權限關係，接著定義 Role 可以執行哪些操作</p>
<pre><code class="language-go">package rbac.authz

# user-role assignments
user_roles := {
  &quot;design_group_kpi_editor&quot;: [&quot;kpi_editor_design&quot;, &quot;viewer_limit_ds&quot;],
  &quot;system_group_kpi_editor&quot;: [&quot;kpi_editor_system&quot;, &quot;viewer_limit_ds&quot;],
  &quot;manufacture_group_kpi_editor&quot;: [&quot;kpi_editor_manufacture&quot;, &quot;viewer&quot;],
  &quot;project_leader&quot;: [&quot;viewer_limit_ds&quot;, &quot;viewer_limit_m&quot;]
}

# role-permissions assignments
role_permissions := {
  &quot;admin&quot;: [
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;design&quot;},
    {&quot;action&quot;: &quot;edit&quot;,  &quot;object&quot;: &quot;design&quot;},
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;system&quot;},
    {&quot;action&quot;: &quot;edit&quot;,  &quot;object&quot;: &quot;system&quot;},
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;manufacture&quot;},
    {&quot;action&quot;: &quot;edit&quot;,  &quot;object&quot;: &quot;manufacture&quot;},
  ],
  &quot;quality_head_design&quot;:[
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;design&quot;},
    {&quot;action&quot;: &quot;edit&quot;,  &quot;object&quot;: &quot;design&quot;},
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;system&quot;},
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;manufacture&quot;},
  ],
  &quot;quality_head_system&quot;:[
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;design&quot;},
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;system&quot;},
    {&quot;action&quot;: &quot;edit&quot;,  &quot;object&quot;: &quot;system&quot;},
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;manufacture&quot;},
  ],
  &quot;quality_head_manufacture&quot;:[
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;design&quot;},
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;system&quot;},
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;manufacture&quot;},
    {&quot;action&quot;: &quot;edit&quot;,  &quot;object&quot;: &quot;manufacture&quot;},
  ],

  &quot;kpi_editor_design&quot;:[
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;design&quot;},
    {&quot;action&quot;: &quot;edit&quot;,  &quot;object&quot;: &quot;design&quot;},
  ],
  &quot;kpi_editor_system&quot;:[
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;system&quot;},
    {&quot;action&quot;: &quot;edit&quot;,  &quot;object&quot;: &quot;system&quot;},
  ],
  &quot;kpi_editor_manufacture&quot;:[
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;manufacture&quot;},
    {&quot;action&quot;: &quot;edit&quot;,  &quot;object&quot;: &quot;manufacture&quot;},
  ],

  &quot;viewer&quot;:[
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;design&quot;},
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;system&quot;},
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;manufacture&quot;},
  ],

  &quot;viewer_limit_ds&quot;:[
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;design&quot;},
    {&quot;action&quot;: &quot;view_all&quot;,  &quot;object&quot;: &quot;system&quot;},
  ],

  &quot;viewer_limit_m&quot;:[
    {&quot;action&quot;: &quot;view_l3_project&quot;,  &quot;object&quot;: &quot;manufacture&quot;},
  ],
}</code></pre>
<p>資料準備完成後，接著就是寫政策</p>
<pre><code class="language-go="># logic that implements RBAC.
default allow = false
allow {
  # lookup the list of roles for the user
  roles := user_roles[input.user[_]]
  # for each role in that list
  r := roles[_]
  # lookup the permissions list for role r
  permissions := role_permissions[r]
  # for each permission
  p := permissions[_]
  # check if the permission granted to r matches the user&#039;s request
  p == {&quot;action&quot;: input.action, &quot;object&quot;: input.object}
}</code></pre>
<p>大家可以看到其中 <code>input</code> 就是上面第一點 Query 條件，可以是任意的 JSON 格式，接著在 allow 裡面開始處理整個政策流程，第一就是拿到 User 是屬於哪些角色，第二就是找到這些角色相對應得權限，最後就是拿 Query 的條件進行比對，最後可以輸出結果 <code>true</code> 或 <code>false</code>。寫完上面 Rego 檔案後，開發者可以下 OPA 執行檔，並且撰寫測試文件，進行驗證，跟 Go 語言一樣，直接檔名加上 <code>_test</code> 即可</p>
<pre><code class="language-go=">test_design_group_kpi_editor {
  allow with input as {&quot;user&quot;: [&quot;design_group_kpi_editor&quot;], &quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;design&quot;}
  allow with input as {&quot;user&quot;: [&quot;design_group_kpi_editor&quot;], &quot;action&quot;: &quot;edit&quot;, &quot;object&quot;: &quot;design&quot;}
  allow with input as {&quot;user&quot;: [&quot;design_group_kpi_editor&quot;], &quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;system&quot;}
  not allow with input as {&quot;user&quot;: [&quot;design_group_kpi_editor&quot;], &quot;action&quot;: &quot;edit&quot;, &quot;object&quot;: &quot;system&quot;}
  not allow with input as {&quot;user&quot;: [&quot;design_group_kpi_editor&quot;], &quot;action&quot;: &quot;view_all&quot;, &quot;object&quot;: &quot;manufacture&quot;}
  not allow with input as {&quot;user&quot;: [&quot;design_group_kpi_editor&quot;], &quot;action&quot;: &quot;edit&quot;, &quot;object&quot;: &quot;manufacture&quot;}
}</code></pre>
<p>像是這樣的格式，接著用 OPA Command 執行測試</p>
<pre><code class="language-bash=">$ opa test -v *.rego
data.rbac.authz.test_design_group_kpi_editor: PASS (8.604833ms)
data.rbac.authz.test_system_group_kpi_editor: PASS (7.260166ms)
data.rbac.authz.test_manufacture_group_kpi_editor: PASS (2.217125ms)
data.rbac.authz.test_project_leader: PASS (1.823833ms)
data.rbac.authz.test_design_group_kpi_editor_and_system_group_kpi_editor: PASS (1.150791ms)
--------------------------------------------------------------------------------
PASS: 5/5</code></pre>
<h2>整合 Go 語言驗證及測試</h2>
<p>上面是透過 OPA 官方的 Command 驗證 Policy 是否正確，接著我們可以整合 Go 語言進行驗證。通常會架設一台 OPA 服務，用來處理授權機制，那現在直接把 Policy 寫進去 Go 執行檔，減少驗證的 Latency。</p>
<pre><code class="language-go=">package main

import (
    &quot;context&quot;
    &quot;log&quot;

    &quot;github.com/open-policy-agent/opa/rego&quot;
)

var policyFile = &quot;example.rego&quot;
var defaultQuery = &quot;x = data.rbac.authz.allow&quot;

type input struct {
    User   string `json:&quot;user&quot;`
    Action string `json:&quot;action&quot;`
    Object string `json:&quot;object&quot;`
}

func main() {
    s := input{
        User:   &quot;design_group_kpi_editor&quot;,
        Action: &quot;view_all&quot;,
        Object: &quot;design&quot;,
    }

    input := map[string]interface{}{
        &quot;user&quot;:   []string{s.User},
        &quot;action&quot;: s.Action,
        &quot;object&quot;: s.Object,
    }

    policy, err := readPolicy(policyFile)
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.TODO()
    query, err := rego.New(
        rego.Query(defaultQuery),
        rego.Module(policyFile, string(policy)),
    ).PrepareForEval(ctx)

    if err != nil {
        log.Fatalf(&quot;initial rego error: %v&quot;, err)
    }

    ok, _ := result(ctx, query, input)
    log.Println(&quot;&quot;, ok)
}

func result(ctx context.Context, query rego.PreparedEvalQuery, input map[string]interface{}) (bool, error) {
    results, err := query.Eval(ctx, rego.EvalInput(input))
    if err != nil {
        log.Fatalf(&quot;evaluation error: %v&quot;, err)
    } else if len(results) == 0 {
        log.Fatal(&quot;undefined result&quot;, err)
        // Handle undefined result.
    } else if result, ok := results[0].Bindings[&quot;x&quot;].(bool); !ok {
        log.Fatalf(&quot;unexpected result type: %v&quot;, result)
    }

    return results[0].Bindings[&quot;x&quot;].(bool), nil
}</code></pre>
<p>其中 readPolicy 可以直接用 go1.16 推出的 embed 套件，將 rego 檔案直接整合進 go binary。</p>
<pre><code class="language-go=">// +build go1.16

package main

import (
    _ &quot;embed&quot;
)

//go:embed example.rego
var policy []byte

func readPolicy(path string) ([]byte, error) {
    return policy, nil
}</code></pre>
<p>撰寫測試，直接在 Go 語言進行測試及資料讀取，以便驗證更多細項功能</p>
<pre><code class="language-go=">package main

import (
    &quot;context&quot;
    &quot;log&quot;
    &quot;os&quot;
    &quot;testing&quot;

    &quot;github.com/open-policy-agent/opa/rego&quot;
)

var query rego.PreparedEvalQuery

func setup() {
    var err error
    policy, err := readPolicy(policyFile)
    if err != nil {
        log.Fatal(err)
    }

    query, err = rego.New(
        rego.Query(defaultQuery),
        rego.Module(policyFile, string(policy)),
    ).PrepareForEval(context.TODO())

    if err != nil {
        log.Fatalf(&quot;initial rego error: %v&quot;, err)
    }
}

func TestMain(m *testing.M) {
    setup()
    code := m.Run()
    os.Exit(code)
}

func Test_result(t *testing.T) {
    ctx := context.TODO()
    type args struct {
        ctx   context.Context
        query rego.PreparedEvalQuery
        input map[string]interface{}
    }
    tests := []struct {
        name    string
        args    args
        want    bool
        wantErr bool
    }{
        {
            name: &quot;test_design_group_kpi_editor_edit_design&quot;,
            args: args{
                ctx:   ctx,
                query: query,
                input: map[string]interface{}{
                    &quot;user&quot;:   []string{&quot;design_group_kpi_editor&quot;},
                    &quot;action&quot;: &quot;edit&quot;,
                    &quot;object&quot;: &quot;design&quot;,
                },
            },
            want:    true,
            wantErr: false,
        },
        {
            name: &quot;test_design_group_kpi_editor_edit_system&quot;,
            args: args{
                ctx:   ctx,
                query: query,
                input: map[string]interface{}{
                    &quot;user&quot;:   []string{&quot;design_group_kpi_editor&quot;},
                    &quot;action&quot;: &quot;edit&quot;,
                    &quot;object&quot;: &quot;system&quot;,
                },
            },
            want:    false,
            wantErr: false,
        },
        {
            name: &quot;test_design_group_kpi_editor_and_system_group_kpi_editor_for_edit_design&quot;,
            args: args{
                ctx:   ctx,
                query: query,
                input: map[string]interface{}{
                    &quot;user&quot;:   []string{&quot;design_group_kpi_editor&quot;, &quot;system_group_kpi_editor&quot;},
                    &quot;action&quot;: &quot;edit&quot;,
                    &quot;object&quot;: &quot;design&quot;,
                },
            },
            want:    true,
            wantErr: false,
        },
        {
            name: &quot;test_design_group_kpi_editor_and_system_group_kpi_editor_for_edit_system&quot;,
            args: args{
                ctx:   ctx,
                query: query,
                input: map[string]interface{}{
                    &quot;user&quot;:   []string{&quot;design_group_kpi_editor&quot;, &quot;system_group_kpi_editor&quot;},
                    &quot;action&quot;: &quot;edit&quot;,
                    &quot;object&quot;: &quot;system&quot;,
                },
            },
            want:    true,
            wantErr: false,
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := result(tt.args.ctx, tt.args.query, tt.args.input)
            if (err != nil) != tt.wantErr {
                t.Errorf(&quot;result() error = %v, wantErr %v&quot;, err, tt.wantErr)
                return
            }
            if got != tt.want {
                t.Errorf(&quot;result() = %v, want %v&quot;, got, tt.want)
            }
        })
    }
}</code></pre>
<h2>心得</h2>
<p>由於網路上教學文件也不多，故自己先寫一篇紀錄基本操作，未來會有更多跟 Go 整合的實際案例，
屆時會再分享給大家。OPA 除了 RBAC 之外，還有更多功能可以在官網上面查詢，個人覺得整合起來應該會相當方便，各種情境幾乎都有考慮到，不單單只有一些特定情境可以使用，至於怎麼擴充到更多情境，就是靠 Rego 撰寫 Policy 語法，並撰寫驗證及測試。本篇所使用的範例程式碼請從<a href="https://github.com/go-training/opa-demo/tree/v0.0.1">這邊下載或觀看</a>。</p>
<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">Related View</h3><ul class="related_post wp_rp"><li data-position="0" data-poid="in-7925" data-post-type="none" ><a href="https://blog.wu-boy.com/2021/05/comunicate-with-open-policy-agent-using-resful-api/" class="wp_rp_title">使用 RESTful API 串接 Open Policy Agent</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="1" data-poid="in-7352" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/04/install-specific-go-version-in-appveyor/" class="wp_rp_title">在 appveyor 內指定 Go 語言編譯版本</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="2" data-poid="in-7901" data-post-type="none" ><a href="https://blog.wu-boy.com/2021/03/debug-performance-issues-using-pyroscope/" class="wp_rp_title">即時效能分析工具 Pyroscope</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="3" data-poid="in-7737" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/06/go-module-cache-variable-gomodcache/" class="wp_rp_title">Go 1.15 新增 Module cache 環境變數</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="4" data-poid="in-7384" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/05/handle-multiple-channel-in-15-minutes/" class="wp_rp_title">15 分鐘學習 Go 語言如何處理多個 Channel 通道</a><small class="wp_rp_comments_count"> (13)</small><br /></li><li data-position="5" data-poid="in-7452" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/08/golang-project-layout-and-practice/" class="wp_rp_title">Go 語言目錄結構與實踐</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="6" data-poid="in-7870" data-post-type="none" ><a href="https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-02/" class="wp_rp_title">初探 Pulumi 上傳靜態網站到 AWS S3 (二)</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="7" data-poid="in-6674" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/03/golang-dependency-management-tool-dep/" class="wp_rp_title">Go 語言官方推出的 dep 使用心得</a><small class="wp_rp_comments_count"> (6)</small><br /></li><li data-position="8" data-poid="in-6869" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/11/gorush-a-push-notification-server-written-in-go/" class="wp_rp_title">Gorush 輕量級手機訊息發送服務</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="9" data-poid="in-7838" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/12/embedding-files-in-go-1-16/" class="wp_rp_title">Go 1.16 推出 Embedding Files</a><small class="wp_rp_comments_count"> (0)</small><br /></li></ul></div></div>
]]></content:encoded>
			<wfw:commentRss>https://blog.wu-boy.com/2021/04/setup-rbac-role-based-access-control-using-open-policy-agent/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>為什麼 signal.Notify 要使用 buffered channel</title>
		<link>https://blog.wu-boy.com/2021/03/why-use-buffered-channel-in-signal-notify/</link>
		<comments>https://blog.wu-boy.com/2021/03/why-use-buffered-channel-in-signal-notify/#respond</comments>
		<pubDate>Tue, 30 Mar 2021 03:19:16 +0000</pubDate>
		<dc:creator><![CDATA[appleboy]]></dc:creator>
				<category><![CDATA[Golang]]></category>

		<guid isPermaLink="false">https://blog.wu-boy.com/?p=7909</guid>
		<description><![CDATA[如果不了解什麼是 buffer 或 unbuffer channel 的朋友們，可以參考這篇文章先做初步了解，本文要跟大家介紹為什麼 signal.Notify 要使用 buffered channel 才可以，底下先來看看如何使用 signal.Notify，當我們要做 graceful shutdown 都會使用到這功能，想要正常關閉服務或連線，透過 signal 可以偵測訊號來源，執行後續相關工作 (關閉 DB 連線，檢查 Job 是否結束 &#8230; 等)。 package main import ( &#34;fmt&#34; &#34;os&#34; &#34;os/signal&#34; ) func main() { // Set up channel on which to send signal notifications. // We must use a buffered channel or risk missing &#8230; <a href="https://blog.wu-boy.com/2021/03/why-use-buffered-channel-in-signal-notify/" class="more-link">Continue reading<span class="screen-reader-text"> "為什麼 signal.Notify 要使用 buffered channel"</span></a>]]></description>
				<content:encoded><![CDATA[<p><a href="https://lh3.googleusercontent.com/jsocHCR9A9yEfDVUTrU0m42_aHhTEVDGW5p5PsQSx7GSlkt3gLjohfXH3S7P7p982332ruU_e-EtW0LwmiuZjvN65VIcyME-zE35C6EM0IV1nqY6KoNw3dwW2djjid3F-T5YgnJothA=w1920-h1080" title="golang logo"><img src="https://lh3.googleusercontent.com/jsocHCR9A9yEfDVUTrU0m42_aHhTEVDGW5p5PsQSx7GSlkt3gLjohfXH3S7P7p982332ruU_e-EtW0LwmiuZjvN65VIcyME-zE35C6EM0IV1nqY6KoNw3dwW2djjid3F-T5YgnJothA=w1920-h1080" alt="golang logo" title="golang logo" /></a></p>
<p>如果不了解什麼是 buffer 或 unbuffer <a href="https://tour.golang.org/concurrency/2">channel</a> 的朋友們，可以參考<a href="https://blog.wu-boy.com/2019/04/understand-unbuffered-vs-buffered-channel-in-five-minutes/">這篇文章</a>先做初步了解，本文要跟大家介紹為什麼 signal.Notify 要使用 buffered channel 才可以，底下先來看看如何使用 signal.Notify，當我們要做 <a href="https://blog.wu-boy.com/2020/02/what-is-graceful-shutdown-in-golang/">graceful shutdown</a> 都會使用到這功能，想要正常關閉服務或連線，透過 signal 可以偵測訊號來源，執行後續相關工作 (關閉 DB 連線，檢查 Job 是否結束 &#8230; 等)。</p>
<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
    &quot;os&quot;
    &quot;os/signal&quot;
)

func main() {
    // Set up channel on which to send signal notifications.
    // We must use a buffered channel or risk missing the signal
    // if we&#039;re not ready to receive when the signal is sent.
    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)

    // Block until a signal is received.
    s := &lt;-c
    fmt.Println(&quot;Got signal:&quot;, s)
}</code></pre>
<p>上面例子可以很清楚看到說明，假如沒有使用 buffered channel 的話，你有一定的風險會沒抓到 Signal。那為什麼會有這段說明呢？底下用其他例子來看看。</p>
<span id="more-7909"></span>
<h2>教學影片</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/uMDmImCZNI4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h2>使用 unbuffered channel</h2>
<p>將程式碼改成如下:</p>
<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
    &quot;os&quot;
    &quot;os/signal&quot;
)

func main() {
    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt)

    // Block until a signal is received.
    s := &lt;-c
    fmt.Println(&quot;Got signal:&quot;, s)
}</code></pre>
<p>執行上述程式碼，然後按下 ctrl + c，沒意外你會看到 <code>Got signal: interrupt</code>，那接著我們在接受 channle 前面有處理一些很複雜的工作，先用 <code>time.Sleep</code> 來測試</p>
<pre><code class="language-go">package main

import (
    &quot;fmt&quot;
    &quot;os&quot;
    &quot;os/signal&quot;
)

func main() {
    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt)

    time.Sleep(5 * time.Second)

    // Block until a signal is received.
    s := &lt;-c
    fmt.Println(&quot;Got signal:&quot;, s)
}</code></pre>
<p>一樣執行程式，然後按下 ctrl + c，你會發現在這五秒內，怎麼按都不會停止，等到五秒後，整個程式也不會終止，需要再按下一次 ctrl + c，這時候程式才會終止，我們預期的是，在前五秒內，按下任何一次 ctrl + c，理論上五秒後要能正常接受到第一次傳來的訊號，底下來看看原因</p>
<h2>形成原因</h2>
<p>我們打開 Golang 的 <code>singal.go</code> 檔案，找到 <code>process</code> func，可以看到部分程式碼</p>
<pre><code class="language-go">    for c, h := range handlers.m {
        if h.want(n) {
            // send but do not block for it
            select {
            case c &lt;- sig:
            default:
            }
        }
    }</code></pre>
<p>可以看到上述程式碼，如果使用 unbuffered channel，那麼在五秒內接收到的任何訊號，都會跑到 default 條件內，所以造成 Channel 不會收到任何值，這也就是為什麼五秒內的任何動作，在五秒後都完全收不到。為了避免這件事情，所以通常我們會將 signal channel 設定為 <code>buffer 1</code>，來避免需要中斷程式時，確保主程式可以收到一個 signal 訊號。</p>
<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">Related View</h3><ul class="related_post wp_rp"><li data-position="0" data-poid="in-7534" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/11/implement-job-queue-using-buffer-channel-in-golang/" class="wp_rp_title">用 Go 語言 buffered channel 實作 Job Queue</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="1" data-poid="in-7352" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/04/install-specific-go-version-in-appveyor/" class="wp_rp_title">在 appveyor 內指定 Go 語言編譯版本</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="2" data-poid="in-7523" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/10/job-queue-in-golang/" class="wp_rp_title">用 Go 語言實作 Job Queue 機制</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="3" data-poid="in-7330" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/04/understand-unbuffered-vs-buffered-channel-in-five-minutes/" class="wp_rp_title">用五分鐘了解什麼是 unbuffered vs buffered channel</a><small class="wp_rp_comments_count"> (5)</small><br /></li><li data-position="4" data-poid="in-7384" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/05/handle-multiple-channel-in-15-minutes/" class="wp_rp_title">15 分鐘學習 Go 語言如何處理多個 Channel 通道</a><small class="wp_rp_comments_count"> (13)</small><br /></li><li data-position="5" data-poid="in-7557" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/11/four-tips-with-select-in-golang/" class="wp_rp_title">Go 語言使用 Select 四大用法</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="6" data-poid="in-7597" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/02/graceful-shutdown-with-multiple-workers/" class="wp_rp_title">[Go 教學] graceful shutdown with multiple workers</a><small class="wp_rp_comments_count"> (3)</small><br /></li><li data-position="7" data-poid="in-7794" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/10/select-multiple-channel-in-golang/" class="wp_rp_title">Go 語言 Select Multiple Channel 注意事項</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="8" data-poid="in-7584" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/01/when-to-use-go-channel-and-goroutine/" class="wp_rp_title">使用 Go Channel 及 Goroutine 時機</a><small class="wp_rp_comments_count"> (2)</small><br /></li><li data-position="9" data-poid="in-7776" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/08/three-ways-to-manage-concurrency-in-go/" class="wp_rp_title">在 Go 語言內管理 Concurrency 的三種方式</a><small class="wp_rp_comments_count"> (0)</small><br /></li></ul></div></div>
]]></content:encoded>
			<wfw:commentRss>https://blog.wu-boy.com/2021/03/why-use-buffered-channel-in-signal-notify/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>即時效能分析工具 Pyroscope</title>
		<link>https://blog.wu-boy.com/2021/03/debug-performance-issues-using-pyroscope/</link>
		<comments>https://blog.wu-boy.com/2021/03/debug-performance-issues-using-pyroscope/#respond</comments>
		<pubDate>Mon, 01 Mar 2021 13:20:59 +0000</pubDate>
		<dc:creator><![CDATA[appleboy]]></dc:creator>
				<category><![CDATA[Golang]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[pprof]]></category>
		<category><![CDATA[Pyroscope]]></category>

		<guid isPermaLink="false">https://blog.wu-boy.com/?p=7901</guid>
		<description><![CDATA[當網站上線後，流量增加或短暫功能故障，都會造成使用者體驗相當不好，而這時該怎麼快速找到效能的瓶頸呢？通常 CPU 衝到 100% 時，有時候也蠻難複製及找出關鍵問題點。本篇會介紹一套工具叫 pyroscope，讓開發者可以快速找到效能瓶頸的程式碼。之前也寫了相關的效能瓶頸文章，可以參考看看『Go 語言用 pprof 找出程式碼效能瓶頸』或『善用 Go 語言效能測試工具來提升執行效率』，上述兩篇都是針對 Go 語言的效能分析文章，而 pyroscope 目前可以支援在 Python, Ruby 或 Go 的環境。底下筆者會針對 Go 環境做介紹。 影片分享 如果對於課程內容有興趣，可以參考底下課程。 Go 語言基礎實戰 (開發, 測試及部署) 一天學會 DevOps 自動化測試及部署 DOCKER 容器開發部署實戰 如果需要搭配購買請直接透過 FB 聯絡我，直接匯款（價格再減 100） 什麼是 Pyroscope？ Pyroscope 是一套開源的效能即時監控平台，簡單的 Server 及 Agent 架構，讓開發者可以輕鬆監控代碼效能，不管你要找 10 秒或幾分鐘內的效能數據，都可以快速的即時呈現，開發者也不用在意裝了此監控會造成任何效能上的負擔。Pyroscope 背後的儲存採用 Badger 這套 Key-Value 資料庫，效能上是非常好的。目前只有支援 3 種語言 (Python, &#8230; <a href="https://blog.wu-boy.com/2021/03/debug-performance-issues-using-pyroscope/" class="more-link">Continue reading<span class="screen-reader-text"> "即時效能分析工具 Pyroscope"</span></a>]]></description>
				<content:encoded><![CDATA[<p><img src="https://lh3.googleusercontent.com/PIRK3Qj4WiToHgB0QDDf6fMHZxDmEswjWJdTIfVJ8xY7UtSau5C0mosjALev5qbJMflIfrIWsC3bPjjxHRRWQNAiFZSCLbVlin-r1-ICV-lOnopbnpRj4BiMKJnTbslpdo-n3CS2zbQ=w1920-h1080" alt="" /></p>
<p>當網站上線後，流量增加或短暫功能故障，都會造成使用者體驗相當不好，而這時該怎麼快速找到效能的瓶頸呢？通常 CPU 衝到 100% 時，有時候也蠻難複製及找出關鍵問題點。本篇會介紹一套工具叫 <a href="https://pyroscope.io/">pyroscope</a>，讓開發者可以快速找到效能瓶頸的程式碼。之前也寫了相關的效能瓶頸文章，可以參考看看『<a href="https://blog.wu-boy.com/2020/06/golang-benchmark-pprof/">Go 語言用 pprof 找出程式碼效能瓶頸</a>』或『<a href="https://blog.wu-boy.com/2020/11/improve-parser-performance-using-go-benchmark-tool/">善用 Go 語言效能測試工具來提升執行效率</a>』，上述兩篇都是針對 <a href="https://golang.org">Go 語言</a>的效能分析文章，而 pyroscope 目前可以支援在 <a href="https://www.python.org/">Python</a>, <a href="https://www.ruby-lang.org/en/">Ruby</a> 或 <a href="https://golang.org">Go</a> 的環境。底下筆者會針對 Go 環境做介紹。</p>
<span id="more-7901"></span>
<h2>影片分享</h2>
<p>如果對於課程內容有興趣，可以參考底下課程。</p>
<ul>
<li><a href="https://www.udemy.com/course/golang-fight/?couponCode=202102">Go 語言基礎實戰 (開發, 測試及部署)</a></li>
<li><a href="https://www.udemy.com/course/devops-oneday/?couponCode=202103">一天學會 DevOps 自動化測試及部署</a></li>
<li><a href="https://www.udemy.com/course/docker-practice/?couponCode=202103">DOCKER 容器開發部署實戰</a></li>
</ul>
<p>如果需要搭配購買請直接透過 <a href="http://facebook.com/appleboy46">FB 聯絡我</a>，直接匯款（價格再減 <strong>100</strong>）</p>
<h2>什麼是 Pyroscope？</h2>
<p>Pyroscope 是一套開源的效能即時監控平台，簡單的 Server 及 Agent 架構，讓開發者可以輕鬆監控代碼效能，不管你要找 10 秒或幾分鐘內的效能數據，都可以快速的即時呈現，開發者也不用在意裝了此監控會造成任何效能上的負擔。Pyroscope 背後的儲存採用 <a href="https://github.com/dgraph-io/badger">Badger</a> 這套 Key-Value 資料庫，效能上是非常好的。目前只有支援 3 種語言 (Python, Ruby 及 Go) 未來會<a href="https://github.com/pyroscope-io/pyroscope/issues/8">預計支援 NodeJS</a>。假設您還沒導入任何效能分析工具或平台，那 Pyroscope 會是您最好的選擇。</p>
<h2>Pyroscope 架構</h2>
<p>如果你有打算找效能分析工具平台，Pyroscope 提供了三大優勢，讓開發者可以放心使用</p>
<ol>
<li>低 CPU 使用率，不會影響既有平台</li>
<li>可儲存好幾年的資料，並且用 10 秒這麼細的顆粒度看資料</li>
<li>壓縮儲存資料，減少浪費硬碟空間</li>
</ol>
<p>架構只有分 Server 跟 Agent 而已，可以參考底下架構圖，除了 Go 語言之外，Python 跟 Ruby App 都是透過 pyroscope 指令啟動相關 app 來監控系統效能。底下架構圖<a href="https://pyroscope.io/docs/how-pyroscope-works">來自官方網站</a></p>
<p><img src="https://lh3.googleusercontent.com/PIRK3Qj4WiToHgB0QDDf6fMHZxDmEswjWJdTIfVJ8xY7UtSau5C0mosjALev5qbJMflIfrIWsC3bPjjxHRRWQNAiFZSCLbVlin-r1-ICV-lOnopbnpRj4BiMKJnTbslpdo-n3CS2zbQ=w1920-h1080" alt="" /></p>
<h2>啟動 Pyroscope 服務</h2>
<p>啟動方式有兩種，第一是直接用 docker 指令啟動</p>
<pre><code class="language-sh">docker run -it -p 4040:4040 pyroscope/pyroscope:latest server</code></pre>
<p>另一種可以用 docker-compose 啟動</p>
<pre><code class="language-yaml">---
services:
  pyroscope:
    image: &quot;pyroscope/pyroscope:latest&quot;
    ports:
      - &quot;4040:4040&quot;
    command:
      - &quot;server&quot;</code></pre>
<h2>在 Go 裡面安裝 agent</h2>
<p>本篇用 Go 語言當作範例，先 import package</p>
<pre><code class="language-go">import &quot;github.com/pyroscope-io/pyroscope/pkg/agent/profiler&quot;</code></pre>
<p>接著在 <code>main.go</code> 寫入底下程式碼即可:</p>
<pre><code class="language-go">profiler.Start(profiler.Config{
    ApplicationName: &quot;simple.golang.app&quot;,
    ServerAddress:   &quot;http://pyroscope:4040&quot;,
})</code></pre>
<p>其中 <code>http://pyroscope</code> 可以換成自訂的 hostname 即可，接著打開上述網址就可以看到效能監控的畫面了</p>
<p><img src="https://lh3.googleusercontent.com/8B47gH8UdtdkP-d2nFv-kYx113Bc0r0hQ3YkPL1WJSSmqBv10J7oOXznVXOUSpj-Bd0MWCFvzw8XXhX3mEUMr8sc7ZkPQKC740ASwYAxotFDt5siStTCJXpPEcswIxTTHPA_M6uj4y4=w1920-h1080" alt="" /></p>
<p>透過畫面可以快速找到是 SQL 或哪個函式執行很久</p>
<p><img src="https://lh3.googleusercontent.com/E117n5ulSa3Iuxp_I1b-1hMjiFWx-r83xHIZ0cUHw4SDCd7MR8-VgU8FSVmXnWsetL8LZroMv016c_Llr9H3GD3gDdBtxUhKTaOD2_nqsgZD3iScy671dtDsF8Y5tmznBdYn_9sf_xU=w1920-h1080" alt="" /></p>
<h2>心得</h2>
<p>這套工具相當方便，在 Go 語言雖然可以用 pprof 快速找到問題，但是難免還是需要手動的一些地方才可以查出效能瓶頸，有了這套平台，就可以將全部 App 都進行監控，當使用者有任何問題，就可以快速透過 Pyroscope 查看看哪邊程式碼出了問題。</p>
<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">Related View</h3><ul class="related_post wp_rp"><li data-position="0" data-poid="in-7721" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/06/golang-benchmark-pprof/" class="wp_rp_title">Go 語言用 pprof 找出程式碼效能瓶頸</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="1" data-poid="in-7352" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/04/install-specific-go-version-in-appveyor/" class="wp_rp_title">在 appveyor 內指定 Go 語言編譯版本</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="2" data-poid="in-7916" data-post-type="none" ><a href="https://blog.wu-boy.com/2021/04/setup-rbac-role-based-access-control-using-open-policy-agent/" class="wp_rp_title">初探 Open Policy Agent 實作 RBAC (Role-based access control) 權限控管</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="3" data-poid="in-7040" data-post-type="none" ><a href="https://blog.wu-boy.com/2018/06/how-to-write-benchmark-in-go/" class="wp_rp_title">如何在 Go 語言內寫效能測試</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="4" data-poid="in-7452" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/08/golang-project-layout-and-practice/" class="wp_rp_title">Go 語言目錄結構與實踐</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="5" data-poid="in-6869" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/11/gorush-a-push-notification-server-written-in-go/" class="wp_rp_title">Gorush 輕量級手機訊息發送服務</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="6" data-poid="in-7813" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/11/improve-parser-performance-using-go-benchmark-tool/" class="wp_rp_title">善用 Go 語言效能測試工具來提升執行效率</a><small class="wp_rp_comments_count"> (2)</small><br /></li><li data-position="7" data-poid="in-7534" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/11/implement-job-queue-using-buffer-channel-in-golang/" class="wp_rp_title">用 Go 語言 buffered channel 實作 Job Queue</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="8" data-poid="in-7683" data-post-type="none" ><a href="https://blog.wu-boy.com/2020/04/switch-graphql-go-to-gqlgen-in-golang/" class="wp_rp_title">[Go 語言] 從 graphql-go 轉換到 gqlgen</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="9" data-poid="in-6634" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/01/new-git-code-hosting-option-gitea/" class="wp_rp_title">開發者另類的自架 Git 服務選擇: Gitea</a><small class="wp_rp_comments_count"> (5)</small><br /></li></ul></div></div>
]]></content:encoded>
			<wfw:commentRss>https://blog.wu-boy.com/2021/03/debug-performance-issues-using-pyroscope/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>兩台電腦透過 croc 工具來傳送檔案 (簡單, 加密, 快速)</title>
		<link>https://blog.wu-boy.com/2021/02/share-files-between-two-computer-using-croc-tool/</link>
		<comments>https://blog.wu-boy.com/2021/02/share-files-between-two-computer-using-croc-tool/#comments</comments>
		<pubDate>Tue, 16 Feb 2021 03:46:33 +0000</pubDate>
		<dc:creator><![CDATA[appleboy]]></dc:creator>
				<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[croc]]></category>
		<category><![CDATA[devops]]></category>
		<category><![CDATA[golang]]></category>

		<guid isPermaLink="false">https://blog.wu-boy.com/?p=7891</guid>
		<description><![CDATA[兩台電腦之間該如何傳送檔案，其實方法有超多種的，像是 FTP 或透過 SSH 方式來傳送檔案，但是這些方法步驟都有點複雜，FTP 需要架設 FTP 服務，SSH 要學習 SCP 指令，那有沒有更好的方式從單一電腦點對點傳送檔案到另一台呢？傳送過程需要快速又要安全，本篇介紹一套用 Go 語言寫的工具叫 croc，詳細的介紹可以參考看看作者的 Blog 介紹，此工具有底下功能及優勢。 影片教學 00:00​ 兩台電腦該如何傳送檔案? 01:40​ 介紹 croc 工具優勢跟特點 03:24​ 如何使用 croc 工具 05:34​ 自行產生 secret code 方式 06:36​ croc relay server 介紹 07:11​ 自行架設 relay server 10:25​ 心得 (簡單, 快速, 安全) 工具特點及優勢 用 relay 方式讓任意兩台電腦傳送檔案 點對點加密 (使用 PAKE) 跨平台傳送檔案 (Windows, &#8230; <a href="https://blog.wu-boy.com/2021/02/share-files-between-two-computer-using-croc-tool/" class="more-link">Continue reading<span class="screen-reader-text"> "兩台電腦透過 croc 工具來傳送檔案 (簡單, 加密, 快速)"</span></a>]]></description>
				<content:encoded><![CDATA[<p><img src="https://lh3.googleusercontent.com/VHlioiLpLfqBnh5PnGjYhU6l7dZ2V3PURxz5RfulFL74xYYr4kL5EgkOa-OfLQyIALLgmRIcKlLHnbIENFe0cyv82XQW5ia0HgeNwm4u2ijNsjSQQjkrY4JJjloB_pHTOT-EtxzxOlw=w1920-h1080" alt="croc" /></p>
<p>兩台電腦之間該如何傳送檔案，其實方法有超多種的，像是 FTP 或透過 SSH 方式來傳送檔案，但是這些方法步驟都有點複雜，FTP 需要架設 FTP 服務，SSH 要學習 SCP 指令，那有沒有更好的方式從單一電腦點對點傳送檔案到另一台呢？傳送過程需要快速又要安全，本篇介紹一套用 <a href="https://golang.org">Go 語言</a>寫的工具叫 <a href="https://github.com/schollz/croc">croc</a>，詳細的介紹可以參考看看<a href="https://schollz.com/blog/croc6/">作者的 Blog 介紹</a>，此工具有底下功能及優勢。</p>
<span id="more-7891"></span>
<h2>影片教學</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/lq9SRsxse4o" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<ul>
<li>00:00​ 兩台電腦該如何傳送檔案?</li>
<li>01:40​ 介紹 croc 工具優勢跟特點</li>
<li>03:24​ 如何使用 croc 工具</li>
<li>05:34​ 自行產生 secret code 方式</li>
<li>06:36​ croc relay server 介紹</li>
<li>07:11​ 自行架設 relay server</li>
<li>10:25​ 心得 (簡單, 快速, 安全)</li>
</ul>
<h2>工具特點及優勢</h2>
<ol>
<li>用 relay 方式讓任意兩台電腦傳送檔案</li>
<li>點對點加密 (使用 <a href="https://en.wikipedia.org/wiki/Password-authenticated_key_agreement">PAKE</a>)</li>
<li>跨平台傳送檔案 (Windows, Linux, Mac)</li>
<li>一次可以傳送多個檔案或整個目錄</li>
<li>支援續傳</li>
<li>不需要自行架設服務或使用 port-forwarding 相關技術</li>
<li>優先使用 ipv6，而 ipv4 當作備援</li>
<li>可以使用 socks5 proxy</li>
</ol>
<h2>使用方式</h2>
<p>使用方式如同底下這張圖所表示</p>
<p><img src="https://lh3.googleusercontent.com/VHlioiLpLfqBnh5PnGjYhU6l7dZ2V3PURxz5RfulFL74xYYr4kL5EgkOa-OfLQyIALLgmRIcKlLHnbIENFe0cyv82XQW5ia0HgeNwm4u2ijNsjSQQjkrY4JJjloB_pHTOT-EtxzxOlw=w1920-h1080" alt="croc" /></p>
<p>傳送端只需要執行 <code>croc send file.txt</code> 即可</p>
<pre><code class="language-sh">$ croc send ~/Downloads/data.csv
Sending &#039;data.csv&#039; (632.9 kB)
Code is: cabinet-rodeo-mayday
On the other computer run

croc cabinet-rodeo-mayday</code></pre>
<p>上面可以看到會自動產生一個 <code>secret code</code>，接著在另外一台電腦執行底下指令</p>
<pre><code class="language-sh">$ croc cabinet-rodeo-mayday
Accept &#039;data.csv&#039; (632.9 kB)? (y/n) y

Receiving (&lt;-111.243.108.9:51032)</code></pre>
<p>當然你可以自訂 <code>secret code</code></p>
<pre><code class="language-sh">croc send --code appleboy ~/Downloads/data.csv</code></pre>
<p>由於此工具是透過 relay server 方式來進行傳送，所以指令會預設連到官方所架設的服務器</p>
<pre><code class="language-go">// DEFAULT_RELAY is the default relay used (can be set using --relay)
var (
    DEFAULT_RELAY      = &quot;croc.schollz.com&quot;
    DEFAULT_RELAY6     = &quot;croc6.schollz.com&quot;
    DEFAULT_PORT       = &quot;9009&quot;
    DEFAULT_PASSPHRASE = &quot;pass123&quot;
)</code></pre>
<p>假設你想要自己架設 relay server 呢？很簡單，這工具也讓開發者很快架設一台 relay server，只要執行底下</p>
<pre><code class="language-sh">$ croc relay
[info]  2021/02/16 11:38:59 starting croc relay version v8.6.7-05640cd
[info]  2021/02/16 11:38:59 starting TCP server on 9010
[info]  2021/02/16 11:38:59 starting TCP server on 9012
[info]  2021/02/16 11:38:59 starting TCP server on 9009
[info]  2021/02/16 11:38:59 starting TCP server on 9013
[info]  2021/02/16 11:38:59 starting TCP server on 9011</code></pre>
<p>可以指定單一 port:</p>
<pre><code class="language-sh">$ croc relay --ports 3001
[info]  2021/02/16 11:39:22 starting croc relay version v8.6.7-05640cd
[info]  2021/02/16 11:39:22 starting TCP server on 3001</code></pre>
<p>接著在傳送檔案也要跟著換掉 relay server</p>
<pre><code class="language-sh">$ croc --relay 127.0.0.1:3001 send ~/Downloads/data.csv
Sending &#039;data.csv&#039; (632.9 kB)
Code is: saddle-origin-horizon
On the other computer run

croc --relay 127.0.0.1:3001 saddle-origin-horizon</code></pre>
<p>可以看到需要加上 <code>--relay 127.0.0.1:3001</code> 就可以完成了，所以很簡單的架設 relay server，這樣官方服務掛了，你也可以在任意一台電腦裝上 relay server 了。</p>
<h2>心得</h2>
<p>croc 工具強調的就是: 簡單 + 安全 + 快速，三大優勢，讓大家可以更容易點對點傳送檔案，加上 CLI 工具在任何平台都可以下載 (Windows, Mac, 及 Linux)，只需要一個指令就可以裝好此工具，跟其他朋友傳送檔案。未來會再多介紹一些好用工具給大家。</p>
<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">Related View</h3><ul class="related_post wp_rp"><li data-position="0" data-poid="in-6481" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/08/golang-tesing-on-jenkins/" class="wp_rp_title">在 Jenkins 跑 Golang 測試</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="1" data-poid="in-6493" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/08/build-microservices-in-golang/" class="wp_rp_title">2016 COSCUP 用 Golang 寫 Microservices</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="2" data-poid="in-6452" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/07/new-coverage-service-codecov-io/" class="wp_rp_title">新的 code coverage 線上服務 codecov.io</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="3" data-poid="in-6791" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/08/microservice-in-go/" class="wp_rp_title">用 Go 語言打造微服務架構</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="4" data-poid="in-6569" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/11/send-line-notification-using-docker-written-in-golang/" class="wp_rp_title">用 Docker 發送 Line 訊息</a><small class="wp_rp_comments_count"> (10)</small><br /></li><li data-position="5" data-poid="in-6714" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/04/build-minimal-docker-container-using-multi-stage-for-go-app/" class="wp_rp_title">用 Docker Multi-Stage 編譯出 Go 語言最小 Image</a><small class="wp_rp_comments_count"> (2)</small><br /></li><li data-position="6" data-poid="in-6907" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/11/downsize-node-modules-with-golang/" class="wp_rp_title">用 Go 語言減少 node_modules 容量來加速部署</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="7" data-poid="in-7352" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/04/install-specific-go-version-in-appveyor/" class="wp_rp_title">在 appveyor 內指定 Go 語言編譯版本</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="8" data-poid="in-6507" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/09/drone-ci-server-integrate-atlassian-bitbucket-server/" class="wp_rp_title">Drone CI Server 搭配 Atlassian Bitbucket Server (前身 Stash)</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="9" data-poid="in-6804" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/09/why-i-choose-drone-as-ci-cd-tool/" class="wp_rp_title">為什麼我用 Drone 取代 Jenkins 及 GitLab CI</a><small class="wp_rp_comments_count"> (11)</small><br /></li></ul></div></div>
]]></content:encoded>
			<wfw:commentRss>https://blog.wu-boy.com/2021/02/share-files-between-two-computer-using-croc-tool/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>搶救 Terraform State 檔案</title>
		<link>https://blog.wu-boy.com/2021/02/recovering-terraform-state/</link>
		<comments>https://blog.wu-boy.com/2021/02/recovering-terraform-state/#respond</comments>
		<pubDate>Sun, 14 Feb 2021 13:59:28 +0000</pubDate>
		<dc:creator><![CDATA[appleboy]]></dc:creator>
				<category><![CDATA[DevOps]]></category>
		<category><![CDATA[devops]]></category>
		<category><![CDATA[terraform]]></category>

		<guid isPermaLink="false">https://blog.wu-boy.com/?p=7883</guid>
		<description><![CDATA[近期其中一個專案使用 Terraform 來管理 AWS 雲平台，初期預計只有我一個人在使用 Terraform，所以就沒有將 Backend State 放在 AWS S3 進行備份管理，這個粗心大意讓我花了大半時間來搶救 State (.tfstate) 檔案，而搶救過程也是蠻順利的，只是需要花時間用 terraform import 指令將所有的 State 狀態全部轉回來一次，當然不是每個 Resource 都可以正常運作，還是需要搭配一些修正才能全部轉換。 結論: 請使用 terraform import 指令，這是最終解法。 教學影片 00:00 為什麼需要搶救 Terraform state 檔案 02:10 步驟一: 使用 Terraform refres 指令 04:00 步驟二: 使用 Terraform import 指令 09:15 步驟三: 將 State 備份到 S3 上面並且做版本控制 步驟一: 使用 terraform &#8230; <a href="https://blog.wu-boy.com/2021/02/recovering-terraform-state/" class="more-link">Continue reading<span class="screen-reader-text"> "搶救 Terraform State 檔案"</span></a>]]></description>
				<content:encoded><![CDATA[<p><img src="https://lh3.googleusercontent.com/3ZDBZ2vZbRS1NTRzSg1ftpwIhEltm9iPe4-DFNE4y6yuLxbsvxGd6UQfLwcSvHb-AhGBcmtK36NiWBT1BeUzE8ra713qNV-cFnDk2pSVP_mqpz_MG5bpNg0Yx8jZc2-wlkOTb-xk1FE=w1920-h1080" alt="recovery the terraform state file" /></p>
<p>近期其中一個專案使用 Terraform 來管理 AWS 雲平台，初期預計只有我一個人在使用 Terraform，所以就沒有將 <a href="https://www.terraform.io/docs/language/settings/backends/index.html">Backend State</a> 放在 AWS S3 進行備份管理，這個粗心大意讓我花了大半時間來搶救 State (.tfstate) 檔案，而搶救過程也是蠻順利的，只是需要花時間用 <strong><a href="https://www.terraform.io/docs/cli/import/index.html">terraform import</a></strong> 指令將所有的 State 狀態全部轉回來一次，當然不是每個 Resource 都可以正常運作，還是需要搭配一些修正才能全部轉換。</p>
<p>結論: 請使用 <code>terraform import</code> 指令，這是最終解法。</p>
<span id="more-7883"></span>
<h2>教學影片</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/I0fZIvQmWgE" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<ul>
<li>00:00 為什麼需要搶救 Terraform state 檔案</li>
<li>02:10 步驟一: 使用 Terraform refres 指令</li>
<li>04:00 步驟二: 使用 Terraform import 指令</li>
<li>09:15 步驟三: 將 State 備份到 S3 上面並且做版本控制</li>
</ul>
<h2>步驟一: 使用 terraform refresh</h2>
<p>先用 <a href="https://www.terraform.io/docs/cli/commands/refresh.html">terraform refresh</a> 將所有 tf 檔案內的 <code>data</code> 框架內容讀取進來，像是底下格式:</p>
<pre><code class="language-tf">data &quot;aws_acm_certificate&quot; &quot;example_com&quot; {
  domain = &quot;*.example.com&quot;
  types  = [&quot;IMPORTED&quot;]
}</code></pre>
<p>或是</p>
<pre><code class="language-yaml">data &quot;aws_ami&quot; &quot;ubuntu_16_04&quot; {
  filter {
    name   = &quot;name&quot;
    values = [&quot;ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*&quot;]
  }

  filter {
    name   = &quot;virtualization-type&quot;
    values = [&quot;hvm&quot;]
  }

  filter {
    name   = &quot;image-id&quot;
    values = [&quot;ami-0d97809b54a5f01ba&quot;]
  }

  owners = [&quot;099720109477&quot;] # Canonical
}</code></pre>
<p>這時候你可以看到專案底下多了 <code>terraform.tfstate</code> 檔案，記錄了這些檔案資料。</p>
<h2>步驟二: 用 terraform import</h2>
<p>這邊沒啥技巧，就是使用 terraform import 將剩下的 resource 慢慢匯入進來，此步驟需要花蠻多時間的，請大家慢慢操作跟使用，後續搭配 terraform plan 方式來看看有哪些資源還沒匯入，最重要的是一些機器的資源匯入，像是 EC2, RDS 等.. 這些是非常重要的部分。像是我這邊遇到，原本建立 RDS 的密碼都是透過 <code>random_string</code></p>
<pre><code class="language-yaml">resource &quot;random_string&quot; &quot;db_password&quot; {
  special = false
  length  = 20
}</code></pre>
<p>這邊你就需要把這邊整段拿掉，並且在 RDS 那邊把密碼欄位拿掉，否則你會發現密碼會被重新產生。只要是有產生動態密碼的地方，請務必小心，該拿掉的地方還是要拿掉，最後請務必把 <code>terraform plan</code> 檢查清楚，最後才下 <code>terraform apply</code>。</p>
<h2>步驟三: 上傳 state 到 S3</h2>
<p>有了這次經驗，上述完成步驟二，整個更新 infra 也沒問題後，就需要把本機端的 state 上傳到 AWS S3 進行備份，並且做版本控制啊。打開 <code>main.tf</code></p>
<pre><code class="language-yaml">terraform {
  backend &quot;s3&quot; {
    bucket = &quot;xxxxx.backup&quot;
    key    = &quot;terraform/terraform.tfstate&quot;
    region = &quot;ap-southeast-1&quot;
  }
}</code></pre>
<p>存檔後，直接下 terraform init 就會將檔案直接轉到 AWS S3 上了。</p>
<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">Related View</h3><ul class="related_post wp_rp"><li data-position="0" data-poid="in-7857" data-post-type="none" ><a href="https://blog.wu-boy.com/2021/02/introduction-to-infrastructure-as-code-terraform-vs-pulumi/" class="wp_rp_title">初探 Infrastructure as Code 工具 Terraform vs Pulumi</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="1" data-poid="in-6548" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/10/website-support-http2-using-letsencrypt/" class="wp_rp_title">申請 Let&#8217;s Encrypt 免費憑證讓網站支援 HTTP2</a><small class="wp_rp_comments_count"> (2)</small><br /></li><li data-position="2" data-poid="in-7006" data-post-type="none" ><a href="https://blog.wu-boy.com/2018/04/how-to-use-filter-in-drone/" class="wp_rp_title">[影片教學] 使用 Filter 將專案跑在特定 Drone Agent 服務</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="3" data-poid="in-6539" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/10/ssh-agent-forwarding-proxycommand-tutorial/" class="wp_rp_title">SSH agent forwarding 教學</a><small class="wp_rp_comments_count"> (6)</small><br /></li><li data-position="4" data-poid="in-6791" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/08/microservice-in-go/" class="wp_rp_title">用 Go 語言打造微服務架構</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="5" data-poid="in-6507" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/09/drone-ci-server-integrate-atlassian-bitbucket-server/" class="wp_rp_title">Drone CI Server 搭配 Atlassian Bitbucket Server (前身 Stash)</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="6" data-poid="in-6728" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/06/downsize-node_modules-to-improve-deploy-speed/" class="wp_rp_title">減少 node_modules 大小來加速部署 Node.js 專案</a><small class="wp_rp_comments_count"> (2)</small><br /></li><li data-position="7" data-poid="in-6493" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/08/build-microservices-in-golang/" class="wp_rp_title">2016 COSCUP 用 Golang 寫 Microservices</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="8" data-poid="in-6907" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/11/downsize-node-modules-with-golang/" class="wp_rp_title">用 Go 語言減少 node_modules 容量來加速部署</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="9" data-poid="in-6452" data-post-type="none" ><a href="https://blog.wu-boy.com/2016/07/new-coverage-service-codecov-io/" class="wp_rp_title">新的 code coverage 線上服務 codecov.io</a><small class="wp_rp_comments_count"> (0)</small><br /></li></ul></div></div>
]]></content:encoded>
			<wfw:commentRss>https://blog.wu-boy.com/2021/02/recovering-terraform-state/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>初探 Pulumi 上傳靜態網站到 AWS S3 (二)</title>
		<link>https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-02/</link>
		<comments>https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-02/#comments</comments>
		<pubDate>Thu, 11 Feb 2021 07:55:36 +0000</pubDate>
		<dc:creator><![CDATA[appleboy]]></dc:creator>
				<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[pulumi]]></category>

		<guid isPermaLink="false">https://blog.wu-boy.com/?p=7870</guid>
		<description><![CDATA[上一篇『初探 Pulumi 上傳靜態網站到 AWS S3 (一)』主要介紹 Pulumi 基本使用方式，而本篇會延續上一篇教學把剩下的章節教完，底下是本篇會涵蓋的章節內容: 設定 Pulumi Stack 環境變數 建立第二個 Pulumi Stack 環境 刪除 Pulumi Stack 環境 讓開發者可以自由新增各種不同環境，像是 Testing 或 Develop 環境，以及該如何動態帶入不同環境的變數內容，最後可以透過單一指令將全部資源刪除。 教學影片 00:00​ Pulumi 教學內容簡介 (Stack 介紹) 01:04​ 設定 Pulumi Stack 環境變數 05:04​ 用 Go 語言讀取多個檔案上傳到 AWS S3 08:04​ 用 Pulumi 建立多個開發環境 09:00​ 用 pulumi config 設定環境參數 12:35​ 如何清除單一 Pulumi Stack &#8230; <a href="https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-02/" class="more-link">Continue reading<span class="screen-reader-text"> "初探 Pulumi 上傳靜態網站到 AWS S3 (二)"</span></a>]]></description>
				<content:encoded><![CDATA[<p><a href="https://lh3.googleusercontent.com/pw/ACtC-3f_62JD9fB_bxTcLFJRGhsADlda4hjJjFkzsuDAx0SnMTGZNlX0kl1j4n3WMpjBcPP9BpNOYrIVsy80vqXwjhKSLP7hH_d01FqpdCjA_S9cCdrBXnqE14LndovknJXimWkPHVKo56bcaJgP0SpqDw3Vog=w1283-h571-no?authuser=0" title="cover"><img src="https://lh3.googleusercontent.com/pw/ACtC-3f_62JD9fB_bxTcLFJRGhsADlda4hjJjFkzsuDAx0SnMTGZNlX0kl1j4n3WMpjBcPP9BpNOYrIVsy80vqXwjhKSLP7hH_d01FqpdCjA_S9cCdrBXnqE14LndovknJXimWkPHVKo56bcaJgP0SpqDw3Vog=w1283-h571-no?authuser=0" alt="cover" title="cover" /></a></p>
<p>上一篇『<a href="https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-01/">初探 Pulumi 上傳靜態網站到 AWS S3 (一)</a>』主要介紹 Pulumi 基本使用方式，而本篇會延續上一篇教學把剩下的章節教完，底下是本篇會涵蓋的章節內容:</p>
<ol>
<li>設定 Pulumi Stack 環境變數</li>
<li>建立第二個 Pulumi Stack 環境</li>
<li>刪除 Pulumi Stack 環境</li>
</ol>
<p>讓開發者可以自由新增各種不同環境，像是 Testing 或 Develop 環境，以及該如何動態帶入不同環境的變數內容，最後可以透過單一指令將全部資源刪除。</p>
<span id="more-7870"></span>
<h2>教學影片</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/pMonecsbMOc" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<ul>
<li>00:00​ Pulumi 教學內容簡介 (Stack 介紹)</li>
<li>01:04​ 設定 Pulumi Stack 環境變數</li>
<li>05:04​ 用 Go 語言讀取多個檔案上傳到 AWS S3</li>
<li>08:04​ 用 Pulumi 建立多個開發環境</li>
<li>09:00​ 用 pulumi config 設定環境參數</li>
<li>12:35​ 如何清除單一 Pulumi Stack 環境</li>
<li>15:30​ Pulumi 管理 AWS 心得</li>
</ul>
<h2>設定 Pulumi Stack 環境變數</h2>
<p>大家可以看到，現在所有 <code>main.go</code> 的程式碼，都是直接 hardcode 的，那怎麼透過一些環境變數來動態改變設定呢？這時候可以透過 pulumi config 指令來調整喔，底下來看看怎麼實作，假設我們要讀取的 index.html 放在其他目錄底下，該怎麼動態調整？</p>
<h3>步驟一: 撰寫讀取 Config 函式</h3>
<pre><code class="language-go">func getEnv(ctx *pulumi.Context, key string, fallback ...string) string {
    if value, ok := ctx.GetConfig(key); ok {
        return value
    }

    if len(fallback) &gt; 0 {
        return fallback[0]
    }

    return &quot;&quot;
}</code></pre>
<p>pulumi 的 context 內有一個讀取環境變數函式叫 <code>GetConfig</code>，接著我們在設計一個 fallback 當作 default 回傳值。底下設定一個變數 <code>s3:siteDir</code></p>
<pre><code class="language-sh">pulumi config set s3:siteDir production</code></pre>
<p>打開 <code>Pulumi.dev.yaml</code> 可以看到</p>
<pre><code class="language-yaml">config:
  aws:profile: demo
  aws:region: ap-northeast-1
  s3:siteDir: production</code></pre>
<p>接著將程式碼改成如下:</p>
<pre><code class="language-go">        site := getEnv(ctx, &quot;s3:siteDir&quot;, &quot;content&quot;)
        index := path.Join(site, &quot;index.html&quot;)
        _, err = s3.NewBucketObject(ctx, &quot;index.html&quot;, &amp;s3.BucketObjectArgs{
            Bucket:      bucket.Bucket,
            Source:      pulumi.NewFileAsset(index),
            Acl:         pulumi.String(&quot;public-read&quot;),
            ContentType: pulumi.String(mime.TypeByExtension(path.Ext(index))),
        })</code></pre>
<h3>步驟二: 更新 Infrastructure</h3>
<pre><code class="language-sh">$ pulumi up
Previewing update (dev)

View Live: https://app.pulumi.com/appleboy/demo/dev/previews/d76d2f9b-16c8-4bfd-820d-d5368d29f592

     Type                    Name        Plan       Info
     pulumi:pulumi:Stack     demo-dev
 ~   └─ aws:s3:BucketObject  index.html  update     [diff: ~source]

Resources:
    ~ 1 to update
    2 unchanged

Do you want to perform this update? details
  pulumi:pulumi:Stack: (same)
    [urn=urn:pulumi:dev::demo::pulumi:pulumi:Stack::demo-dev]
    ~ aws:s3/bucketObject:BucketObject: (update)
        [id=index.html]
        [urn=urn:pulumi:dev::demo::aws:s3/bucketObject:BucketObject::index.html]
      - source: asset(file:77aab46) { content/index.html }
      + source: asset(file:01c09f4) { production/index.html }</code></pre>
<p>可以看到 source 會被換成 <code>production/index.html</code></p>
<h3>步驟三: 讀取更多檔案</h3>
<p>整個 Web 專案肯定不止一個檔案，所以再來改一下原本的讀取檔案列表流程</p>
<pre><code class="language-go">        site := getEnv(ctx, &quot;s3:siteDir&quot;, &quot;content&quot;)
        files, err := ioutil.ReadDir(site)
        if err != nil {
            return err
        }

        for _, item := range files {
            name := item.Name()
            if _, err = s3.NewBucketObject(ctx, name, &amp;s3.BucketObjectArgs{
                Bucket:      bucket.Bucket,
                Source:      pulumi.NewFileAsset(filepath.Join(site, name)),
                Acl:         pulumi.String(&quot;public-read&quot;),
                ContentType: pulumi.String(mime.TypeByExtension(path.Ext(filepath.Join(site, name)))),
            }); err != nil {
                return err
            }
        }</code></pre>
<p>執行部署</p>
<pre><code class="language-sh">     Type                    Name        Status      Info
     pulumi:pulumi:Stack     demo-dev
 +   ├─ aws:s3:BucketObject  about.html  created
 ~   └─ aws:s3:BucketObject  index.html  updated     [diff: ~source]

Outputs:
    bucketEndpoint: &quot;foobar-1234.s3-website-ap-northeast-1.amazonaws.com&quot;
    bucketID      : &quot;foobar-1234&quot;
    bucketName    : &quot;foobar-1234&quot;

Resources:
    + 1 created
    ~ 1 updated
    2 changes. 2 unchanged

Duration: 9s</code></pre>
<p>完整程式碼如下:</p>
<pre><code class="language-go">package main

import (
    &quot;io/ioutil&quot;
    &quot;mime&quot;
    &quot;path&quot;
    &quot;path/filepath&quot;

    &quot;github.com/pulumi/pulumi-aws/sdk/v3/go/aws/s3&quot;
    &quot;github.com/pulumi/pulumi/sdk/v2/go/pulumi&quot;
)

func main() {
    pulumi.Run(func(ctx *pulumi.Context) error {
        // Create an AWS resource (S3 Bucket)
        bucket, err := s3.NewBucket(ctx, &quot;my-bucket&quot;, &amp;s3.BucketArgs{
            Bucket: pulumi.String(&quot;foobar-1234&quot;),
            Website: s3.BucketWebsiteArgs{
                IndexDocument: pulumi.String(&quot;index.html&quot;),
            },
        })
        if err != nil {
            return err
        }

        site := getEnv(ctx, &quot;s3:siteDir&quot;, &quot;content&quot;)
        files, err := ioutil.ReadDir(site)
        if err != nil {
            return err
        }

        for _, item := range files {
            name := item.Name()
            if _, err = s3.NewBucketObject(ctx, name, &amp;s3.BucketObjectArgs{
                Bucket:      bucket.Bucket,
                Source:      pulumi.NewFileAsset(filepath.Join(site, name)),
                Acl:         pulumi.String(&quot;public-read&quot;),
                ContentType: pulumi.String(mime.TypeByExtension(path.Ext(filepath.Join(site, name)))),
            }); err != nil {
                return err
            }
        }

        // Export the name of the bucket
        ctx.Export(&quot;bucketID&quot;, bucket.ID())
        ctx.Export(&quot;bucketName&quot;, bucket.Bucket)
        ctx.Export(&quot;bucketEndpoint&quot;, bucket.WebsiteEndpoint)

        return nil
    })
}

func getEnv(ctx *pulumi.Context, key string, fallback ...string) string {
    if value, ok := ctx.GetConfig(key); ok {
        return value
    }

    if len(fallback) &gt; 0 {
        return fallback[0]
    }

    return &quot;&quot;
}</code></pre>
<h2>建立第二個 Pulumi Stack 環境</h2>
<p>在 Pulumi 可以很簡單的建立多種環境，像是 Testing 或 Production，只要將動態變數抽出來設定成 config 即可。底下來看看怎麼建立全先的環境，這步驟在 Pulumi 叫做 Stack。前面已經建立一個 dev 環境，現在我們要建立一個全新環境來部署 Testing 或 Production 該如何做呢？</p>
<h3>步驟一: 建立全新 Stack 環境</h3>
<p>透過 pulumi stack 可以建立全新環境</p>
<pre><code class="language-sh">$ pulumi stack ls
NAME  LAST UPDATE   RESOURCE COUNT  URL
dev*  1 minute ago  5               https://app.pulumi.com/appleboy/demo/dev</code></pre>
<p>建立 stack</p>
<pre><code class="language-sh">$ pulumi stack init prod
Created stack &#039;prod&#039;
$ pulumi stack ls
NAME   LAST UPDATE   RESOURCE COUNT  URL
dev    1 minute ago  5               https://app.pulumi.com/appleboy/demo/dev
prod*  n/a           n/a             https://app.pulumi.com/appleboy/demo/prod</code></pre>
<p>設定參數</p>
<pre><code class="language-sh">pulumi config set s3:siteDir www
pulumi config set aws:profile demo
pulumi config set aws:region ap-northeast-1</code></pre>
<h3>步驟二: 建立 www 內容</h3>
<p>建立 <code>content/www</code> 目錄，一樣放上 index.htm + about.html</p>
<pre><code class="language-html">&lt;html&gt;
  &lt;body&gt;
    &lt;h1&gt;Hello Pulumi S3 Bucket From New Stack&lt;/h1&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>about.html</p>
<pre><code class="language-html">&lt;html&gt;
  &lt;body&gt;
    &lt;h1&gt;About us From New Stack&lt;/h1&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<h3>步驟三: 部署 New Stack</h3>
<p>先看看 Preview 結果</p>
<pre><code class="language-sh">$ pulumi up
Previewing update (prod)

View Live: https://app.pulumi.com/appleboy/demo/prod/previews/3b85a340-0e71-455e-9b96-48dc38538d18

     Type                    Name        Plan
 +   pulumi:pulumi:Stack     demo-prod   create
 +   ├─ aws:s3:Bucket        my-bucket   create
 +   ├─ aws:s3:BucketObject  index.html  create
 +   └─ aws:s3:BucketObject  about.html  create

Resources:
    + 4 to create

Do you want to perform this update? details
+ pulumi:pulumi:Stack: (create)
    [urn=urn:pulumi:prod::demo::pulumi:pulumi:Stack::demo-prod]
    + aws:s3/bucket:Bucket: (create)
        [urn=urn:pulumi:prod::demo::aws:s3/bucket:Bucket::my-bucket]
        acl         : &quot;private&quot;
        bucket      : &quot;my-bucket-ba8088c&quot;
        forceDestroy: false
        website     : {
            indexDocument: &quot;index.html&quot;
        }
    + aws:s3/bucketObject:BucketObject: (create)
        [urn=urn:pulumi:prod::demo::aws:s3/bucketObject:BucketObject::index.html]
        acl         : &quot;public-read&quot;
        bucket      : &quot;my-bucket-ba8088c&quot;
        contentType : &quot;text/html; charset=utf-8&quot;
        forceDestroy: false
        key         : &quot;index.html&quot;
        source      : asset(file:460188b) { www/index.html }
    + aws:s3/bucketObject:BucketObject: (create)
        [urn=urn:pulumi:prod::demo::aws:s3/bucketObject:BucketObject::about.html]
        acl         : &quot;public-read&quot;
        bucket      : &quot;my-bucket-ba8088c&quot;
        contentType : &quot;text/html; charset=utf-8&quot;
        forceDestroy: false
        key         : &quot;about.html&quot;
        source      : asset(file:376c42a) { www/about.html }</code></pre>
<p>如果看起來沒問題，就可以直接執行了</p>
<pre><code class="language-sh">Updating (prod)

View Live: https://app.pulumi.com/appleboy/demo/prod/updates/1

     Type                    Name        Status
 +   pulumi:pulumi:Stack     demo-prod   created
 +   ├─ aws:s3:Bucket        my-bucket   created
 +   ├─ aws:s3:BucketObject  about.html  created
 +   └─ aws:s3:BucketObject  index.html  created

Outputs:
    bucketEndpoint: &quot;my-bucket-a7044ab.s3-website-ap-northeast-1.amazonaws.com&quot;
    bucketID      : &quot;my-bucket-a7044ab&quot;
    bucketName    : &quot;my-bucket-a7044ab&quot;

Resources:
    + 4 created

Duration: 18s</code></pre>
<p>最後用 curl 執行看看</p>
<pre><code class="language-sh">$ curl -v $(pulumi stack output bucketEndpoint)
*   Trying 52.219.8.20...
* TCP_NODELAY set
* Connected to my-bucket-a7044ab.s3-website-ap-northeast-1.amazonaws.com (52.219.8.20) port 80 (#0)
> GET / HTTP/1.1
> Host: my-bucket-a7044ab.s3-website-ap-northeast-1.amazonaws.com
> User-Agent: curl/7.64.1
> Accept: */*
>
&lt; HTTP/1.1 200 OK
&lt; x-amz-id-2: oGxc+rLPi3kLOZslMsOmJqPY/WGeMoxX9sXJDRj4wlJlGVq+7pMx3ers71jxnDiDkeM9JRrd+T8=
&lt; x-amz-request-id: 528235DDFF40F365
&lt; Date: Thu, 11 Feb 2021 04:49:21 GMT
&lt; Last-Modified: Thu, 11 Feb 2021 04:48:41 GMT
&lt; ETag: &quot;ae41d1b3f0aeef6a490e1b2edc74d2b5&quot;
&lt; Content-Type: text/html; charset=utf-8
&lt; Content-Length: 85
&lt; Server: AmazonS3
&lt;
&lt;html&gt;
  &lt;body&gt;
    &lt;h1&gt;Hello Pulumi S3 Bucket From New Stack&lt;/h1&gt;
  &lt;/body&gt;
&lt;/html&gt;
* Connection #0 to host my-bucket-a7044ab.s3-website-ap-northeast-1.amazonaws.com left intact
* Closing connection 0</code></pre>
<h2>刪除 Pulumi Stack 環境</h2>
<p>最後步驟就是要學習怎麼一鍵刪除整個 Infrastructure 環境。現在我們已經建立兩個 Stack 環境，該怎麼移除？</p>
<h3>步驟一: 刪除所有資源</h3>
<p>用 <code>pulumi destroy</code> 指令可以刪除全部資源</p>
<pre><code class="language-sh">Previewing destroy (prod)

View Live: https://app.pulumi.com/appleboy/demo/prod/previews/92f9c4a4-f4a9-464d-be27-5040aff295ae

     Type                    Name        Plan
 -   pulumi:pulumi:Stack     demo-prod   delete
 -   ├─ aws:s3:BucketObject  about.html  delete
 -   ├─ aws:s3:BucketObject  index.html  delete
 -   └─ aws:s3:Bucket        my-bucket   delete

Outputs:
  - bucketEndpoint: &quot;my-bucket-a7044ab.s3-website-ap-northeast-1.amazonaws.com&quot;
  - bucketID      : &quot;my-bucket-a7044ab&quot;
  - bucketName    : &quot;my-bucket-a7044ab&quot;

Resources:
    - 4 to delete

Do you want to perform this destroy? details
- aws:s3/bucketObject:BucketObject: (delete)
    [id=about.html]
    [urn=urn:pulumi:prod::demo::aws:s3/bucketObject:BucketObject::about.html]
- aws:s3/bucketObject:BucketObject: (delete)
    [id=index.html]
    [urn=urn:pulumi:prod::demo::aws:s3/bucketObject:BucketObject::index.html]
- aws:s3/bucket:Bucket: (delete)
    [id=my-bucket-a7044ab]
    [urn=urn:pulumi:prod::demo::aws:s3/bucket:Bucket::my-bucket]
- pulumi:pulumi:Stack: (delete)
    [urn=urn:pulumi:prod::demo::pulumi:pulumi:Stack::demo-prod]
    --outputs:--
  - bucketEndpoint: &quot;my-bucket-a7044ab.s3-website-ap-northeast-1.amazonaws.com&quot;
  - bucketID      : &quot;my-bucket-a7044ab&quot;
  - bucketName    : &quot;my-bucket-a7044ab&quot;</code></pre>
<p>選擇 <code>yse</code> 移除所以資源</p>
<pre><code class="language-sh">Destroying (prod)

View Live: https://app.pulumi.com/appleboy/demo/prod/updates/2

     Type                    Name        Status
 -   pulumi:pulumi:Stack     demo-prod   deleted
 -   ├─ aws:s3:BucketObject  index.html  deleted
 -   ├─ aws:s3:BucketObject  about.html  deleted
 -   └─ aws:s3:Bucket        my-bucket   deleted

Outputs:
  - bucketEndpoint: &quot;my-bucket-a7044ab.s3-website-ap-northeast-1.amazonaws.com&quot;
  - bucketID      : &quot;my-bucket-a7044ab&quot;
  - bucketName    : &quot;my-bucket-a7044ab&quot;

Resources:
    - 4 deleted

Duration: 7s</code></pre>
<h3>步驟二: 移除 Stack 設定</h3>
<p>上面步驟只是把所有資源移除，但是你還是保留了所以 stack history 操作，請看</p>
<pre><code class="language-sh">$ pulumi stack history
Version: 2
UpdateKind: destroy
Status: succeeded
Message: chore(pulumi): 設定 Pulumi Stack 環境變數
+0-4~0 0 Updated 1 minute ago took 8s
    exec.kind: cli
    git.author: Bo-Yi Wu
    git.author.email: xxxxxxxx@gmail.com
    git.committer: Bo-Yi Wu
    git.committer.email: xxxxxxxx@gmail.com
    git.dirty: true
    git.head: 9d9f8182abefb0e90656ca45065bc07a8a3431f4
    git.headName: refs/heads/main
    vcs.kind: github.com
    vcs.owner: go-training
    vcs.repo: infrastructure-as-code-workshop

Version: 1
UpdateKind: update
Status: succeeded
Message: chore(pulumi): 設定 Pulumi Stack 環境變數
+4-0~0 0 Updated 8 minutes ago took 18s
    exec.kind: cli
    git.author: Bo-Yi Wu
    git.author.email: xxxxxxxx@gmail.com
    git.committer: Bo-Yi Wu
    git.committer.email: xxxxxxxx@gmail.com
    git.dirty: true
    git.head: 437e94e130ee3d31eb80075dd237cc17d09255d1
    git.headName: refs/heads/main
    vcs.kind: github.com
    vcs.owner: go-training
    vcs.repo: infrastructure-as-code-workshop</code></pre>
<p>要整個完整移除，請務必要執行底下指令</p>
<pre><code class="language-sh">pulumi stack rm</code></pre>
<p>最後的確認</p>
<pre><code class="language-sh">$ pulumi stack rm
This will permanently remove the &#039;prod&#039; stack!
Please confirm that this is what you&#039;d like to do by typing (&quot;prod&quot;):</code></pre>
<h3>移除其他的 Stack</h3>
<p>按照上面的步驟重新移除其他的 Stack，先使用底下指令列出還有哪些 Stack:</p>
<pre><code class="language-sh">$ pulumi stack ls
NAME  LAST UPDATE     RESOURCE COUNT  URL
dev   24 minutes ago  5               https://app.pulumi.com/appleboy/demo/dev</code></pre>
<p>選擇 Stack</p>
<pre><code class="language-sh">pulumi stack select dev</code></pre>
<p>接著重複上面一跟二步驟即可</p>
<h2>心得</h2>
<p>本篇跟<a href="https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-01/">上一篇</a>教學剛好涵蓋了整個 Pulumi 的基本使用方式，如果你還在選擇要用 <a href="https://www.terraform.io/">Terraform</a> 還是 <a href="https://www.pulumi.com/">Pulumi</a>，甚至 AWS 所推出的 <a href="https://docs.aws.amazon.com/cdk/latest/guide/home.html">CDK</a>，很推薦你嘗試看看 Pulumi，未來會介紹更多 Pulumi 進階的使用方式，或者像是部署 <a href="https://kubernetes.io/">Kubernetes</a> .. 等，能使用自己喜歡的語言寫 Infra 是一件令人舒服的事情。</p>
<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">Related View</h3><ul class="related_post wp_rp"><li data-position="0" data-poid="in-7866" data-post-type="none" ><a href="https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-01/" class="wp_rp_title">初探 Pulumi 上傳靜態網站到 AWS S3 (一)</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="1" data-poid="in-7857" data-post-type="none" ><a href="https://blog.wu-boy.com/2021/02/introduction-to-infrastructure-as-code-terraform-vs-pulumi/" class="wp_rp_title">初探 Infrastructure as Code 工具 Terraform vs Pulumi</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="2" data-poid="in-7352" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/04/install-specific-go-version-in-appveyor/" class="wp_rp_title">在 appveyor 內指定 Go 語言編譯版本</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="3" data-poid="in-7452" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/08/golang-project-layout-and-practice/" class="wp_rp_title">Go 語言目錄結構與實踐</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="4" data-poid="in-7029" data-post-type="none" ><a href="https://blog.wu-boy.com/2018/06/drone-kubernetes-with-golang/" class="wp_rp_title">Drone 搭配 Kubernetes 部署 Go 語言項目</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="5" data-poid="in-6791" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/08/microservice-in-go/" class="wp_rp_title">用 Go 語言打造微服務架構</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="6" data-poid="in-7405" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/07/speed-up-go-module-download-using-go-proxy-athens/" class="wp_rp_title">架設 Go Proxy 服務加速 go module 下載速度</a><small class="wp_rp_comments_count"> (7)</small><br /></li><li data-position="7" data-poid="in-7250" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/02/deploy-golang-app-to-heroku/" class="wp_rp_title">快速部署網站到 Heroku 雲平台</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="8" data-poid="in-6869" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/11/gorush-a-push-notification-server-written-in-go/" class="wp_rp_title">Gorush 輕量級手機訊息發送服務</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="9" data-poid="in-7891" data-post-type="none" ><a href="https://blog.wu-boy.com/2021/02/share-files-between-two-computer-using-croc-tool/" class="wp_rp_title">兩台電腦透過 croc 工具來傳送檔案 (簡單, 加密, 快速)</a><small class="wp_rp_comments_count"> (1)</small><br /></li></ul></div></div>
]]></content:encoded>
			<wfw:commentRss>https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-02/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>初探 Pulumi 上傳靜態網站到 AWS S3 (一)</title>
		<link>https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-01/</link>
		<comments>https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-01/#comments</comments>
		<pubDate>Thu, 11 Feb 2021 07:29:56 +0000</pubDate>
		<dc:creator><![CDATA[appleboy]]></dc:creator>
				<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[pulumi]]></category>

		<guid isPermaLink="false">https://blog.wu-boy.com/?p=7866</guid>
		<description><![CDATA[上一篇作者提到了兩套 Infrastructure as Code 工具，分別是 Terraform 跟 Pulumi，大家對於前者可能會是比較熟悉，那本篇用一個實際案例『建立 AWS S3 並上傳靜態網站』來跟大家分享如何從無開始一步一步使用 Pulumi。本教學使用的程式碼都可以在 GitHub 上面瀏覽及下載。教學會拆成七個章節: 建立 Pulumi 新專案 設定 AWS 環境 初始化 Pulumi 架構 (建立 S3 Bucket) 更新 AWS 架構 (S3 Hosting) 設定 Pulumi Stack 環境變數 (教學二) 建立第二個 Pulumi Stack 環境 (教學二) 刪除 Pulumi Stack 環境 (教學二) 教學影片 00:00​ Pulumi 應用實作簡介 01:30​ 章節一: 用 Pulumi 建立新專案 &#8230; <a href="https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-01/" class="more-link">Continue reading<span class="screen-reader-text"> "初探 Pulumi 上傳靜態網站到 AWS S3 (一)"</span></a>]]></description>
				<content:encoded><![CDATA[<p><a href="https://lh3.googleusercontent.com/pw/ACtC-3f_62JD9fB_bxTcLFJRGhsADlda4hjJjFkzsuDAx0SnMTGZNlX0kl1j4n3WMpjBcPP9BpNOYrIVsy80vqXwjhKSLP7hH_d01FqpdCjA_S9cCdrBXnqE14LndovknJXimWkPHVKo56bcaJgP0SpqDw3Vog=w1283-h571-no?authuser=0" title="cover"><img src="https://lh3.googleusercontent.com/pw/ACtC-3f_62JD9fB_bxTcLFJRGhsADlda4hjJjFkzsuDAx0SnMTGZNlX0kl1j4n3WMpjBcPP9BpNOYrIVsy80vqXwjhKSLP7hH_d01FqpdCjA_S9cCdrBXnqE14LndovknJXimWkPHVKo56bcaJgP0SpqDw3Vog=w1283-h571-no?authuser=0" alt="cover" title="cover" /></a></p>
<p>上一篇作者提到了兩套 <a href="https://www.pulumi.com/docs/get-started/install/">Infrastructure as Code</a> 工具，分別是 <a href="https://www.terraform.io/">Terraform</a> 跟 <a href="https://www.pulumi.com/">Pulumi</a>，大家對於前者可能會是比較熟悉，那本篇用一個實際案例『建立 AWS S3 並上傳靜態網站』來跟大家分享如何從無開始一步一步使用 Pulumi。本教學使用的程式碼都可以在 <a href="https://github.com/go-training/infrastructure-as-code-workshop/tree/main/pulumi/labs/lab01-modern-infrastructure-as-code">GitHub 上面瀏覽及下載</a>。教學會拆成七個章節:</p>
<ol>
<li>建立 Pulumi 新專案</li>
<li>設定 AWS 環境</li>
<li>初始化 Pulumi 架構 (建立 S3 Bucket)</li>
<li>更新 AWS 架構 (S3 Hosting)</li>
<li>設定 Pulumi Stack 環境變數 (<a href="https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-02/">教學二</a>)</li>
<li>建立第二個 Pulumi Stack 環境 (<a href="https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-02/">教學二</a>)</li>
<li>刪除 Pulumi Stack 環境 (<a href="https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-02/">教學二</a>)</li>
</ol>
<span id="more-7866"></span>
<h2>教學影片</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/ujkXfPsU3Is" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<ul>
<li>00:00​ Pulumi 應用實作簡介</li>
<li>01:30​ 章節一: 用 Pulumi 建立新專案</li>
<li>02:39​ 用 Pulumi CLI 初始化專案</li>
<li>04:47​ 介紹 Pulumi 產生的 AWS Go 目錄結構內容</li>
<li>06:10​ 章節二: 設定 AWS 環境</li>
<li>08:36​ 章節三: 建立 AWS S3 Bucket</li>
<li>13:44​ 指定 S3 Bucket 名稱</li>
<li>16:09​ 章節四: 將 S3 Bucket 變成 Web Host</li>
<li>16:25​ 上傳 index.html 到 S3 Bucket 內</li>
<li>18:22​ 設定 S3 Bucket 為 Web Host 讀取 index.html</li>
<li>19:17​ 取得 AWS S3 的 Web URL</li>
<li>20:55​ 修改 S3 Object 的 Permission (ACL)</li>
<li>22:45​ 心得感想</li>
</ul>
<h2>用 Pulumi 建立新專案</h2>
<h3>步驟一: 安裝 Pulumi CLI 工具</h3>
<p>首先你要在自己電腦安裝上 Pulumi CLI 工具，請參考<a href="https://www.pulumi.com/docs/get-started/install/">官方網站</a>，根據您的作業環境有不同的安裝方式，底下以 Mac 環境為主</p>
<pre><code class="language-sh">brew install pulumi</code></pre>
<p>透過 brew 即可安裝成功，那升級工具透過底下即可</p>
<pre><code class="language-sh">brew upgrade pulumi</code></pre>
<p>或者您沒有使用 brew，也可以透過 curl 安裝</p>
<pre><code class="language-sh">curl -fsSL https://get.pulumi.com | sh</code></pre>
<p>測試 CLI 指令</p>
<pre><code class="language-sh">$ pulumi version
v2.20.0</code></pre>
<p>有看到版本資訊就是安裝成功了</p>
<h3>步驟二: 初始化專案</h3>
<p>透過 <code>pulumi new -h</code> 可以看到說明</p>
<pre><code class="language-sh">Usage:
  pulumi new [template|url] [flags]

Flags:
  -c, --config stringArray        Config to save
      --config-path               Config keys contain a path to a property in a map or list to set
  -d, --description string        The project description; if not specified, a prompt will request it
      --dir string                The location to place the generated project; if not specified, the current directory is used
  -f, --force                     Forces content to be generated even if it would change existing files
  -g, --generate-only             Generate the project only; do not create a stack, save config, or install dependencies
  -h, --help                      help for new
  -n, --name string               The project name; if not specified, a prompt will request it
  -o, --offline                   Use locally cached templates without making any network requests
      --secrets-provider string   The type of the provider that should be used to encrypt and decrypt secrets (possible choices: default, passphrase, awskms, azurekeyvault, gcpkms, hashivault) (default &quot;default&quot;)
  -s, --stack string              The stack name; either an existing stack or stack to create; if not specified, a prompt will request it
  -y, --yes                       Skip prompts and proceed with default values</code></pre>
<p>可以選擇的 Template 超多，那我們這次用 AWS 搭配 Go 語言的 Temaplate 當作範例</p>
<pre><code class="language-sh">$ pulumi new aws-go --dir demo
This command will walk you through creating a new Pulumi project.

Enter a value or leave blank to accept the (default), and press &lt;ENTER&gt;.
Press ^C at any time to quit.

project name: (demo)
project description: (A minimal AWS Go Pulumi program)
Created project &#039;demo&#039;

Please enter your desired stack name.
To create a stack in an organization, use the format &lt;org-name&gt;/&lt;stack-name&gt; (e.g. `acmecorp/dev`).
stack name: (dev)
Created stack &#039;dev&#039;

aws:region: The AWS region to deploy into: (us-east-1) ap-northeast-1
Saved config

Installing dependencies...

Finished installing dependencies

Your new project is ready to go! ✨

To perform an initial deployment, run &#039;cd demo&#039;, then, run &#039;pulumi up&#039;</code></pre>
<h3>步驟三: 檢查專案目錄結構</h3>
<pre><code class="language-sh">└── demo
    ├── Pulumi.dev.yaml
    ├── Pulumi.yaml
    ├── go.mod
    ├── go.sum
    └── main.go</code></pre>
<p>其中 <code>main.go</code> 就是主程式檔案</p>
<pre><code class="language-go">package main

import (
    &quot;github.com/pulumi/pulumi-aws/sdk/v3/go/aws/s3&quot;
    &quot;github.com/pulumi/pulumi/sdk/v2/go/pulumi&quot;
)

func main() {
    pulumi.Run(func(ctx *pulumi.Context) error {
        // Create an AWS resource (S3 Bucket)
        bucket, err := s3.NewBucket(ctx, &quot;my-bucket&quot;, nil)
        if err != nil {
            return err
        }

        // Export the name of the bucket
        ctx.Export(&quot;bucketName&quot;, bucket.ID())
        return nil
    })
}</code></pre>
<h2>設定 AWS 環境</h2>
<p>在使用 Pulumi 之前要先把 AWS 環境建立好</p>
<h3>前置作業</h3>
<p>請先將 AWS 環境設定完畢，請用 <code>aws configure</code> 完成 profile 設定</p>
<pre><code class="language-sh">aws configure --profile demo</code></pre>
<h3>步驟一: 設定 AWS Region</h3>
<p>可以參考 <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions">AWS 官方的 Available Regions</a>，並且透過 Pulumi CLI 做調整</p>
<pre><code class="language-sh">cd demo &amp;&amp; pulumi config set aws:region ap-northeast-1</code></pre>
<p>切換到 demo 目錄，並執行 <code>pulumi config</code></p>
<h3>步驟二: 設定 AWS Profile</h3>
<p>如果你有很多環境需要設定，請使用 AWS Profile 作切換，不要用 default profile。其中 <code>demo</code> 為 profile 名稱</p>
<pre><code class="language-sh">pulumi config set aws:profile demo</code></pre>
<h2>初始化 Pulumi 架構 (建立 S3 Bucket)</h2>
<h3>步驟一: 建立新的 S3 Bucket</h3>
<pre><code class="language-go">        bucket, err := s3.NewBucket(ctx, &quot;my-bucket&quot;, nil)
        if err != nil {
            return err
        }</code></pre>
<h3>步驟二: 執行 Pulumi CLI 預覽</h3>
<p>透過底下指令可以直接預覽每個操作步驟所做的改變:</p>
<pre><code class="language-sh">pulumi up</code></pre>
<p>可以看到底下預覽:</p>
<pre><code class="language-sh">Previewing update (dev)

View Live: https://app.pulumi.com/appleboy/demo/dev/previews/db6a9e4e-f391-4cc4-b50c-408319b3d8e2

     Type                 Name       Plan
 +   pulumi:pulumi:Stack  demo-dev   create
 +   └─ aws:s3:Bucket     my-bucket  create

Resources:
    + 2 to create

Do you want to perform this update?  [Use arrows to move, enter to select, type to filter]
  yes
> no
  details</code></pre>
<p>選擇最後的 details:</p>
<pre><code class="language-sh">Do you want to perform this update? details
+ pulumi:pulumi:Stack: (create)
    [urn=urn:pulumi:dev::demo::pulumi:pulumi:Stack::demo-dev]
    + aws:s3/bucket:Bucket: (create)
        [urn=urn:pulumi:dev::demo::aws:s3/bucket:Bucket::my-bucket]
        acl         : &quot;private&quot;
        bucket      : &quot;my-bucket-e3d8115&quot;
        forceDestroy: false</code></pre>
<p>可以看到更詳細的建立步驟及權限，在此步驟可以詳細知道 Pulumi 會怎麼設定 AWS 架構，透過此預覽方式避免人為操作失誤。</p>
<h3>步驟三: 執行部署</h3>
<p>看完上面的預覽，我們最後就直接執行:</p>
<pre><code class="language-sh">Do you want to perform this update? yes
Updating (dev)

View Live: https://app.pulumi.com/appleboy/demo/dev/updates/3

     Type                 Name       Status
 +   pulumi:pulumi:Stack  demo-dev   created
 +   └─ aws:s3:Bucket     my-bucket  created

Outputs:
    bucketName: &quot;my-bucket-9dd3052&quot;

Resources:
    + 2 created

Duration: 17s</code></pre>
<p>透過上述 UI 也可以看到蠻多詳細的資訊</p>
<p><img src="https://i2.wp.com/i.imgur.com/MkY5BZC.png?w=840&#038;ssl=1" alt="" data-recalc-dims="1" />
<img src="https://i2.wp.com/i.imgur.com/jpVpjmm.png?w=840&#038;ssl=1" alt="" data-recalc-dims="1" /></p>
<h3>步驟四: 顯示更多 Bucket 詳細資訊</h3>
<pre><code class="language-go">        // Export the name of the bucket
        ctx.Export(&quot;bucketID&quot;, bucket.ID())
        ctx.Export(&quot;bucketName&quot;, bucket.Bucket)</code></pre>
<p>執行 <code>pulumi up</code></p>
<pre><code class="language-go">Updating (dev)

View Live: https://app.pulumi.com/appleboy/demo/dev/updates/4

     Type                 Name      Status
     pulumi:pulumi:Stack  demo-dev

Outputs:
  + bucketID  : &quot;my-bucket-9dd3052&quot;
    bucketName: &quot;my-bucket-9dd3052&quot;

Resources:
    2 unchanged

Duration: 7s</code></pre>
<h3>步驟五: 更新 Bucket 名稱</h3>
<pre><code class="language-go">func main() {
    pulumi.Run(func(ctx *pulumi.Context) error {
        // Create an AWS resource (S3 Bucket)
        bucket, err := s3.NewBucket(ctx, &quot;my-bucket&quot;, &amp;s3.BucketArgs{
            Bucket: pulumi.String(&quot;foobar-1234&quot;),
        })
        if err != nil {
            return err
        }

        // Export the name of the bucket
        ctx.Export(&quot;bucketID&quot;, bucket.ID())
        ctx.Export(&quot;bucketName&quot;, bucket.Bucket)
        return nil
    })
}</code></pre>
<p>透過 <code>pulumi up</code></p>
<pre><code class="language-sh">Previewing update (dev)

View Live: https://app.pulumi.com/appleboy/demo/dev/previews/7180c121-235c-40cc-9ae2-d0f68455296f

     Type                 Name       Plan        Info
     pulumi:pulumi:Stack  demo-dev
 +-  └─ aws:s3:Bucket     my-bucket  replace     [diff: ~bucket]

Outputs:
  ~ bucketID  : &quot;my-bucket-9dd3052&quot; =&gt; output&lt;string&gt;
  ~ bucketName: &quot;my-bucket-9dd3052&quot; =&gt; &quot;foobar-1234&quot;

Resources:
    +-1 to replace
    1 unchanged

Do you want to perform this update? details
  pulumi:pulumi:Stack: (same)
    [urn=urn:pulumi:dev::demo::pulumi:pulumi:Stack::demo-dev]
    --aws:s3/bucket:Bucket: (delete-replaced)
        [id=my-bucket-9dd3052]
        [urn=urn:pulumi:dev::demo::aws:s3/bucket:Bucket::my-bucket]
    +-aws:s3/bucket:Bucket: (replace)
        [id=my-bucket-9dd3052]
        [urn=urn:pulumi:dev::demo::aws:s3/bucket:Bucket::my-bucket]
      ~ bucket: &quot;my-bucket-9dd3052&quot; =&gt; &quot;foobar-1234&quot;
    ++aws:s3/bucket:Bucket: (create-replacement)
        [id=my-bucket-9dd3052]
        [urn=urn:pulumi:dev::demo::aws:s3/bucket:Bucket::my-bucket]
      ~ bucket: &quot;my-bucket-9dd3052&quot; =&gt; &quot;foobar-1234&quot;
    --outputs:--
  ~ bucketID  : &quot;my-bucket-9dd3052&quot; =&gt; output&lt;string&gt;
  ~ bucketName: &quot;my-bucket-9dd3052&quot; =&gt; &quot;foobar-1234&quot;</code></pre>
<p>可以看到系統會砍掉舊的，在建立一個新的 bucket</p>
<h2>更新 AWS 架構 (S3 Hosting)</h2>
<p>上個步驟教大家如何建立 Infra 架構，那這單元教大家如何將使用 S3 當一個簡單的 Web Hosting。</p>
<ol>
<li>將 index.html 放入 S3 內</li>
<li>設定 S3 當作 Web Hosting</li>
<li>測試 S3 Hosting</li>
</ol>
<h3>步驟一: 建立 index.html 放入 S3 內</h3>
<p>建立 <code>content/index.html</code> 檔案，內容如下</p>
<pre><code class="language-html">&lt;html&gt;
  &lt;body&gt;
    &lt;h1&gt;Hello Pulumi S3 Bucket&lt;/h1&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>修改 <code>main.go</code>，將 <code>index.html</code> 加入到 S3 bucket 內</p>
<pre><code class="language-go">        index := path.Join(&quot;content&quot;, &quot;index.html&quot;)
        _, err = s3.NewBucketObject(ctx, &quot;index.html&quot;, &amp;s3.BucketObjectArgs{
            Bucket: bucket.Bucket,
            Source: pulumi.NewFileAsset(index),
        })

        if err != nil {
            return err
        }</code></pre>
<p>其中目錄結構如下</p>
<pre><code class="language-sh">├── demo
│   ├── Pulumi.dev.yaml
│   ├── Pulumi.yaml
│   ├── content
│   │   └── index.html
│   ├── go.mod
│   ├── go.sum
│   └── main.go</code></pre>
<p>部署到 S3 Bucket 內</p>
<pre><code class="language-sh">$ pulumi up
Previewing update (dev)

View Live: https://app.pulumi.com/appleboy/demo/dev/previews/a0ac1b69-06b9-4109-800d-20618b36e5c8

     Type                    Name        Plan
     pulumi:pulumi:Stack     demo-dev
 +   └─ aws:s3:BucketObject  index.html  create

Resources:
    + 1 to create
    2 unchanged

Do you want to perform this update? details
  pulumi:pulumi:Stack: (same)
    [urn=urn:pulumi:dev::demo::pulumi:pulumi:Stack::demo-dev]
    + aws:s3/bucketObject:BucketObject: (create)
        [urn=urn:pulumi:dev::demo::aws:s3/bucketObject:BucketObject::index.html]
        acl         : &quot;private&quot;
        bucket      : &quot;foobar-1234&quot;
        forceDestroy: false
        key         : &quot;index.html&quot;
        source      : asset(file:77aab46) { content/index.html }</code></pre>
<h3>步驟二: 設定 S3 為 Web Hosting</h3>
<p>修改 main.go</p>
<pre><code class="language-go">        bucket, err := s3.NewBucket(ctx, &quot;my-bucket&quot;, &amp;s3.BucketArgs{
            Bucket: pulumi.String(&quot;foobar-1234&quot;),
            Website: s3.BucketWebsiteArgs{
                IndexDocument: pulumi.String(&quot;index.html&quot;),
            },
        })

        index := path.Join(&quot;content&quot;, &quot;index.html&quot;)
        _, err = s3.NewBucketObject(ctx, &quot;index.html&quot;, &amp;s3.BucketObjectArgs{
            Bucket:      bucket.Bucket,
            Source:      pulumi.NewFileAsset(index),
            Acl:         pulumi.String(&quot;public-read&quot;),
            ContentType: pulumi.String(mime.TypeByExtension(path.Ext(index))),
        })</code></pre>
<p>最後設定輸出 URL:</p>
<pre><code class="language-go">ctx.Export(&quot;bucketEndpoint&quot;, bucket.WebsiteEndpoint)</code></pre>
<p>最後完整程式碼如下:</p>
<pre><code class="language-go">package main

import (
    &quot;mime&quot;
    &quot;path&quot;

    &quot;github.com/pulumi/pulumi-aws/sdk/v3/go/aws/s3&quot;
    &quot;github.com/pulumi/pulumi/sdk/v2/go/pulumi&quot;
)

func main() {
    pulumi.Run(func(ctx *pulumi.Context) error {
        // Create an AWS resource (S3 Bucket)
        bucket, err := s3.NewBucket(ctx, &quot;my-bucket&quot;, &amp;s3.BucketArgs{
            Bucket: pulumi.String(&quot;foobar-1234&quot;),
            Website: s3.BucketWebsiteArgs{
                IndexDocument: pulumi.String(&quot;index.html&quot;),
            },
        })
        if err != nil {
            return err
        }

        index := path.Join(&quot;content&quot;, &quot;index.html&quot;)
        _, err = s3.NewBucketObject(ctx, &quot;index.html&quot;, &amp;s3.BucketObjectArgs{
            Bucket:      bucket.Bucket,
            Source:      pulumi.NewFileAsset(index),
            Acl:         pulumi.String(&quot;public-read&quot;),
            ContentType: pulumi.String(mime.TypeByExtension(path.Ext(index))),
        })

        if err != nil {
            return err
        }

        // Export the name of the bucket
        ctx.Export(&quot;bucketID&quot;, bucket.ID())
        ctx.Export(&quot;bucketName&quot;, bucket.Bucket)
        ctx.Export(&quot;bucketEndpoint&quot;, bucket.WebsiteEndpoint)

        return nil
    })
}</code></pre>
<p>執行 pulumi up</p>
<pre><code class="language-sh">Previewing update (dev)

View Live: https://app.pulumi.com/appleboy/demo/dev/previews/edbaefca-f723-4ac5-aabd-7cb638636612

     Type                    Name        Plan       Info
     pulumi:pulumi:Stack     demo-dev
 ~   ├─ aws:s3:Bucket        my-bucket   update     [diff: +website]
 ~   └─ aws:s3:BucketObject  index.html  update     [diff: ~acl,contentType]

Outputs:
  + bucketEndpoint: output&lt;string&gt;

Resources:
    ~ 2 to update
    1 unchanged

Do you want to perform this update? details
  pulumi:pulumi:Stack: (same)
    [urn=urn:pulumi:dev::demo::pulumi:pulumi:Stack::demo-dev]
    ~ aws:s3/bucket:Bucket: (update)
        [id=foobar-1234]
        [urn=urn:pulumi:dev::demo::aws:s3/bucket:Bucket::my-bucket]
      + website: {
          + indexDocument: &quot;index.html&quot;
        }
    --outputs:--
  + bucketEndpoint: output&lt;string&gt;
    ~ aws:s3/bucketObject:BucketObject: (update)
        [id=index.html]
        [urn=urn:pulumi:dev::demo::aws:s3/bucketObject:BucketObject::index.html]
      ~ acl        : &quot;private&quot; =&gt; &quot;public-read&quot;
      ~ contentType: &quot;binary/octet-stream&quot; =&gt; &quot;text/html; charset=utf-8&quot;
Do you want to perform this update? yes
Updating (dev)

View Live: https://app.pulumi.com/appleboy/demo/dev/updates/7

     Type                    Name        Status      Info
     pulumi:pulumi:Stack     demo-dev
 ~   ├─ aws:s3:Bucket        my-bucket   updated     [diff: +website]
 ~   └─ aws:s3:BucketObject  index.html  updated     [diff: ~acl,contentType]

Outputs:
  + bucketEndpoint: &quot;foobar-1234.s3-website-ap-northeast-1.amazonaws.com&quot;
    bucketID      : &quot;foobar-1234&quot;
    bucketName    : &quot;foobar-1234&quot;

Resources:
    ~ 2 updated
    1 unchanged

Duration: 13s</code></pre>
<h3>步驟三: 測試 URL</h3>
<p>透過底下指令可以拿到 S3 的 URL:</p>
<pre><code class="language-sh">pulumi stack output bucketEndpoint</code></pre>
<p>透過 CURL 指令測試看看</p>
<pre><code class="language-sh">$ curl -v $(pulumi stack output bucketEndpoint)
*   Trying 52.219.16.96...
* TCP_NODELAY set
* Connected to foobar-1234.s3-website-ap-northeast-1.amazonaws.com (52.219.16.96) port 80 (#0)
> GET / HTTP/1.1
> Host: foobar-1234.s3-website-ap-northeast-1.amazonaws.com
> User-Agent: curl/7.64.1
> Accept: */*
>
&lt; HTTP/1.1 200 OK
&lt; x-amz-id-2: 0NrZfFxZNOs+toz0/86FiASG+MyQE6f+KbKNi4wzcDtmn5mTnQoxupVybR464X8Oi6HDMjSU+i8=
&lt; x-amz-request-id: A55BD2534EDC94A9
&lt; Date: Thu, 11 Feb 2021 03:30:42 GMT
&lt; Last-Modified: Thu, 11 Feb 2021 03:29:14 GMT
&lt; ETag: &quot;46e94ba24774d0c4a768f9461e6b9806&quot;
&lt; Content-Type: text/html; charset=utf-8
&lt; Content-Length: 70
&lt; Server: AmazonS3
&lt;
&lt;html&gt;
  &lt;body&gt;
    &lt;h1&gt;Hello Pulumi S3 Bucket&lt;/h1&gt;
  &lt;/body&gt;
&lt;/html&gt;
* Connection #0 to host foobar-1234.s3-website-ap-northeast-1.amazonaws.com left intact</code></pre>
<h2>心得</h2>
<p>上述已經可以將靜態網站放在 AWS S3 上面了，<a href="https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-02/">下一篇</a>會教大家底下三個章節</p>
<ol>
<li>設定 Pulumi Stack 環境變數</li>
<li>建立第二個 Pulumi Stack 環境</li>
<li>刪除 Pulumi Stack 環境</li>
</ol>
<div class="wp_rp_wrap  wp_rp_plain" ><div class="wp_rp_content"><h3 class="related_post_title">Related View</h3><ul class="related_post wp_rp"><li data-position="0" data-poid="in-7870" data-post-type="none" ><a href="https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-02/" class="wp_rp_title">初探 Pulumi 上傳靜態網站到 AWS S3 (二)</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="1" data-poid="in-7352" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/04/install-specific-go-version-in-appveyor/" class="wp_rp_title">在 appveyor 內指定 Go 語言編譯版本</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="2" data-poid="in-7857" data-post-type="none" ><a href="https://blog.wu-boy.com/2021/02/introduction-to-infrastructure-as-code-terraform-vs-pulumi/" class="wp_rp_title">初探 Infrastructure as Code 工具 Terraform vs Pulumi</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="3" data-poid="in-7047" data-post-type="none" ><a href="https://blog.wu-boy.com/2018/07/mkcert-zero-config-tool-to-make-locally-trusted-development-certificates/" class="wp_rp_title">在本機端快速產生網站免費憑證</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="4" data-poid="in-7452" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/08/golang-project-layout-and-practice/" class="wp_rp_title">Go 語言目錄結構與實踐</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="5" data-poid="in-6791" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/08/microservice-in-go/" class="wp_rp_title">用 Go 語言打造微服務架構</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="6" data-poid="in-7441" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/08/minio-remove-healthcheck-script-for-docker-image/" class="wp_rp_title">Minio 從 Docker 容器移除 healthcheck 腳本</a><small class="wp_rp_comments_count"> (0)</small><br /></li><li data-position="7" data-poid="in-7405" data-post-type="none" ><a href="https://blog.wu-boy.com/2019/07/speed-up-go-module-download-using-go-proxy-athens/" class="wp_rp_title">架設 Go Proxy 服務加速 go module 下載速度</a><small class="wp_rp_comments_count"> (7)</small><br /></li><li data-position="8" data-poid="in-6869" data-post-type="none" ><a href="https://blog.wu-boy.com/2017/11/gorush-a-push-notification-server-written-in-go/" class="wp_rp_title">Gorush 輕量級手機訊息發送服務</a><small class="wp_rp_comments_count"> (1)</small><br /></li><li data-position="9" data-poid="in-7029" data-post-type="none" ><a href="https://blog.wu-boy.com/2018/06/drone-kubernetes-with-golang/" class="wp_rp_title">Drone 搭配 Kubernetes 部署 Go 語言項目</a><small class="wp_rp_comments_count"> (0)</small><br /></li></ul></div></div>
]]></content:encoded>
			<wfw:commentRss>https://blog.wu-boy.com/2021/02/upload-static-content-to-aws-s3-using-pulumi-01/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
