20190512のGoに関する記事は4件です。

AWS SESでメールを送信 ~golang編~

1 AWS SDK Goをインストール

go get -u github.com/aws/aws-sdk-go/...

2. AWSのアクセスキー、シークレットアクセスキーを取得

参考
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_access-keys.html

3. クレデンシャル情報をセット

cd アプリケーションディレクトリ
vi .aws/credentials

[default]
aws_access_key_id = *******
aws_secret_access_key = **************

4. 実行ファイルを作成

$ vi test-send.go

package main

import (
    "fmt"

    //go get -u github.com/aws/aws-sdk-go
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/ses"
    "github.com/aws/aws-sdk-go/aws/awserr"
)

const (
    // Replace sender@example.com with your "From" address. 
    // This address must be verified with Amazon SES.
    Sender = "sender@example.com"

    // Replace recipient@example.com with a "To" address. If your account 
    // is still in the sandbox, this address must be verified.
    Recipient = "recipient@example.com"

    // Specify a configuration set. To use a configuration
    // set, comment the next line and line 92.
    //ConfigurationSet = "ConfigSet"

    // The subject line for the email.
    Subject = "Amazon SES Test (AWS SDK for Go)"

    // The HTML body for the email.
    HtmlBody =  "<h1>Amazon SES Test Email (AWS SDK for Go)</h1><p>This email was sent with " +
                "<a href='https://aws.amazon.com/ses/'>Amazon SES</a> using the " +
                "<a href='https://aws.amazon.com/sdk-for-go/'>AWS SDK for Go</a>.</p>"

    //The email body for recipients with non-HTML email clients.
    TextBody = "This email was sent with Amazon SES using the AWS SDK for Go."

    // The character encoding for the email.
    CharSet = "UTF-8"
)

func main() {
    // Create a new session in the us-west-2 region.
    // Replace us-west-2 with the AWS Region you're using for Amazon SES.
    sess, err := session.NewSession(&aws.Config{
        Region:aws.String("us-west-2")},
    )

    // Create an SES session.
    svc := ses.New(sess)

    // Assemble the email.
    input := &ses.SendEmailInput{
        Destination: &ses.Destination{
            CcAddresses: []*string{
            },
            ToAddresses: []*string{
                aws.String(Recipient),
            },
        },
        Message: &ses.Message{
            Body: &ses.Body{
                Html: &ses.Content{
                    Charset: aws.String(CharSet),
                    Data:    aws.String(HtmlBody),
                },
                Text: &ses.Content{
                    Charset: aws.String(CharSet),
                    Data:    aws.String(TextBody),
                },
            },
            Subject: &ses.Content{
                Charset: aws.String(CharSet),
                Data:    aws.String(Subject),
            },
        },
        Source: aws.String(Sender),
            // Uncomment to use a configuration set
            //ConfigurationSetName: aws.String(ConfigurationSet),
    }

    // Attempt to send the email.
    result, err := svc.SendEmail(input)

    // Display error messages if they occur.
    if err != nil {
        if aerr, ok := err.(awserr.Error); ok {
            switch aerr.Code() {
            case ses.ErrCodeMessageRejected:
                fmt.Println(ses.ErrCodeMessageRejected, aerr.Error())
            case ses.ErrCodeMailFromDomainNotVerifiedException:
                fmt.Println(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error())
            case ses.ErrCodeConfigurationSetDoesNotExistException:
                fmt.Println(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error())
            default:
                fmt.Println(aerr.Error())
            }
        } else {
            // Print the error, cast err to awserr.Error to get the Code and
            // Message from an error.
            fmt.Println(err.Error())
        }

        return
    }

    fmt.Println("Email Sent to address: " + Recipient)
    fmt.Println(result)
}

5 実行ファイルのビルド、実行

$ go build -o test-send test-send.go 
$ ./test-go

Email Sent to address: *****
{
  MessageId: "***********"
}

参考

https://docs.aws.amazon.com/ja_jp/sdk-for-go/v1/developer-guide/setting-up.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GCP+GKE+GOのアーキテクチャを理解する

はじめに

この記事は、GCPとGKEとGOを用いたマイクロサービスの開発初心者が、
個々の技術単体ではなくそれらを組み合わせたアーキテクチャを理解することを目的としています。

私自身がGCPとGKEとGOを用いたマイクロサービスの開発方法を勉強し始めている中で、
「GCPもGKEもGOも概要はなんとなく理解した。しかし合わせて考えるとまだつながっていないな」
という状態になっていました。
そこで、GCP+GKE+GOのアーキテクチャを理解しようと思います。

環境項築

Googleのクイックスタートガイド」に沿って環境を構築します。
GCPでGKEを有効にし、クラスタを作成してそれにノード3つ用意してGOアプリをデプロイする・・・という流れになっていますね。
特に躓いた箇所は無かったです。

動作確認

1.GCPのGoogle Cloud Shellで以下のコマンドを実行
 ・hello-server Service を検査
  kubectl get service hello-server

  【結果】
   ?????@ cloudshell:~ (level-datum-236008)$ kubectl get service hello-server
   NAME   TYPE     CLUSTER-IP  EXTERNAL-IP  PORT(S)     AGE
   hello-server LoadBalancer 10.23.248.52 12.345.678.99 8080:30727/TCP 38d

2.ウェブブラウザで、「1」のEXTERNAL-IP(公開されているポートを指定した外部IPアドレス)を使用し、
  GOアプリケーションを表示
  http://[EXTERNAL_IP]:8080
  例 http://12.345.678.99:8080

  【結果】
   Hello, world!
   Version: 1.0.0
   Hostname: hello-server-0123456789-01234

これで、コンテナ化されたGOアプリが
GKEにデプロイされたことを確認できました。

GCP+GKE+GOのアーキテクチャ

GCP_GKE_GO.jpg
 ・GCP(Google Cloud Platform)
   Google がクラウド上で提供するサービス群の総称。

 ・GKE(Google Kubernetes Engine)
   GCP上でKubernetesを簡単に使うことができるサービス。

 ・Kubernetes
   コンテナ(アプリケーション)で構成されたサービスのデプロイやスケーリング、
   構成の管理をしてくれるGoogle製のオープンソース。

 ・GO(GoLang)
   Googleが開発したオープンソースプロジェクトのプログラミング言語。   

 ・クラスター
   ノードを構成しているもの。

 ・ノード
   コンテナが動くサーバー。

 ・コンテナ
   作成したアプリケーションを動かすもの。

おわりに

個々の技術を組み合わせたアーキテクチャを理解することができました。
わけあっていきなりGCP+GKE+GOの組み合わせについて投稿してしまいましたので、
次回はGCPについて投稿しようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GO111MODULE=onなプロジェクトでDockerのビルド時間を短くした工夫

はじめに

Go1.11から採用のGo Modules(vgo)なプロジェクトにて、Dockerのビルドが遅かったのをキャッシュ利用で一応解決できた話。

順を追って

はじめの状態

下記のような構成

.
├── Dockerfile
├── go.mod
├── go.sum
└── main.go
Dockerfile一部抜粋
COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go build -o binary # 遅い!

Goコードの中では大きなOSSプロジェクトライブラリを利用しているので本コードを改修するたびにDockerビルドにて対象パッケージをダウンロードして遅い!

次にvendorをレポジトリに入れてみた

下記のコマンドでパッケージたちをvendorとしてレポジトリに組み込んでみました。

$ go mod vendor
$ tree
.
├── Dockerfile
├── go.mod
├── go.sum
├── main.go
└── vendor
    ├── package1
    ├── package2
Dockerfile一部抜粋
COPY . ./                                                   # ここの通信コストが大きくなってしまった
RUN CGO_ENABLED=0 GOOS=linux go build -mod=vendor -o binary # ここは速くなったけど

これでパッケージを毎回ダウンロードしないのでビルド時間はたしかに短くなった。
しかしレポジトリが巨大になってしまいました。
そして、このプロジェクトではDocker-Machineのリモート接続でDockerを扱っているのでローカルからリモートへの通信コストがvendorのせいでエグいことになってしまいました。
(そもそも.dockerignoreもまともに記載していなかったのでこれはこれで対応。)

解決策 パッケージダウンロードなイメージレイヤを設けてキャッシュ利用

vendorは削除しました。
他言語のプロジェクトと同様な戦略でパッケージに関わる部分を先にダウンロードするイメージレイヤを作ることでキャッシュを活用することにしました。

Dockerfile一部抜粋
ENV GO111MODULE="on"  # Go Modulesを使うと明言する
COPY go.mod go.sum ./ # パッケージ用メタファイルだけコピー
RUN go mod download   # メタファイルに変更がなければキャッシュ利用できる

COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go build -o binary

go.modに修正が入らない限りRUN go mod downloadはキャッシュが利用されるので、一番はじめのビルド時間の遅さは解決できました。
また、新しいパッケージを利用しはじめてそれがgo.modに反映されていなくてもgo buildが代わりにダウンロードするので問題なくイメージがビルドされます。

最後にDockerfileの全体感

Dockerfileの全体としては以下のような感じにしています。
マルチステージングの機能を利用して最終的にできあがるイメージは最小になるよう努めています。

Dockerfile
FROM golang:1.12.5-stretch as builder

WORKDIR /workdir

ENV GO111MODULE="on"
COPY go.mod go.sum ./
RUN go mod download

COPY . ./

RUN CGO_ENABLED=0 GOOS=linux go build -o binary

FROM alpine:3.8

WORKDIR /root/

# Multi stage build function of Docker
COPY --from=builder /workdir/binary .

CMD ["./binary"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt と Go で Software Architecture の実験 ~1日目~

はじめに

Software Architecture について学ぼうとした矢先に、Nuxt.js(Vue.js)とGoでSPA + API(レイヤードアーキテクチャ)でチャットアプリを実装してみたという記事をみつけ、とても勉強になったため、1から自分でも実装してみることにしました。

これはその日記です。1日目はNuxtとGoで簡易的なstatic serverを作成しました。

NuxtのSkeletonの作成

client folerの配下に、nuxt_go_template folderを作成し、その中でnuxtをインストールします。

$ yarn create nuxt-app <project-name>

GoでSkeletonサーバ (static server) の作成

まずは、依存関係管理ツールのdepをインストールします(詳しくはこの記事を参考にしてください。)。

$ cd server
$ dep init

次に、server folder下にインフラ層を担当するinfra folderを作成し、さらにその配下に、routing技術を担当するrouter folderを作成します。

router folderの配下に、router.goを設置し、gorilla muxと呼ばれるhttp request multiplexerのpackageを利用してルーターをインスタンス化します。

router.go
package router

import (
    "github.com/gorilla/mux"
)

// Router is the gorilla router for API.
var Router *mux.Router

func init() {
    // Instantiation for gorilla Router.
    r := mux.NewRouter()

    Router = r
}

server配下のmain.goには静的ファイルをホスティングするサーバーを構築します。

main.go
package main

import (
    "net/http"

    "github.com/hideUW/nuxt_go_template/server/infra/router"
)

func main() {
    // rootに対してnuxtのindex.htmlを返すように設定する。(ServeFile)
    entrypoint := "../client/nuxt_go_template/dist/index.html"
    router.Router.Path("/").HandlerFunc(ServeStaticFile(entrypoint))

    // 静的ファイルの提供
    // $PROROOT/_nuxt/ 以下のファイルに http://localhost:8080/_nuxt/~.html でアクセスできるように設定する。(FileServer)
    router.Router.PathPrefix("/_nuxt/").Handler(
        http.StripPrefix("/_nuxt/", http.FileServer(http.Dir("../client/nuxt_go_template/dist/_nuxt/"))))

    // エラーハンドリング
    if err := http.ListenAndServe(":8080", router.Router); err != nil {
        panic(err.Error())
    }
}

// 静的ファイルをホスティングする。
func ServeStaticFile(entrypoint string) func(w http.ResponseWriter, r *http.Request) {
    fn := func(w http.ResponseWriter, r *http.Request) {
        http.ServeFile(w, r, entrypoint)
    }
    return http.HandlerFunc(fn)
}

Goのhttp.Handlerやhttp.HandlerFuncをちゃんと理解するという記事を参考にすると以下のように動作している事がわかります。

http.Hundler

http.HandlerはServeHTTPというメソッドを定義しているinterfaceです。このServeHTTPメソッドはHTTPリクエストを受けてHTTPレスポンスを返します。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

http.ServeMux のHandleメソッドの引数がこのinterface型になっています。

func (mux *ServeMux) Handle(pattern string, handler Handler) {
    ....
}

HandlerFunc

  • func(ResponseWriter, *Request) を別の型として定義したものです。
  • このHandlerFunc型にはServeHTTP(ResponseWriter, *Request)メソッドが実装されています。func(http.ResponseWriter, *http.Request) のシグネチャの関数を自分で定義しておき、このHandlerFuncにキャストするだけで構造体を宣言することなく http.Handler を実装することができます。
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}
func hello(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "Hello World")
}

func main() {
    mux := http.NewServeMux()
    mux.Handle("/hello", http.HandlerFunc(hello)) // helloをHandlerFuncにキャスト
}
  • helloをhttp.HandlerFuncにキャストしています。
  • http.HandlerFuncは ServeHTTP を実装しているので、ServeMux.Handleの第2引数に指定することが可能です。

おわりに

以下のコマンドを実行すると、Nuxtのindex.htmlが表示されて、ローカルホストを利用したstatic severが完成している事がわかります。

$ cd server
$ go run main.go

image.png

明日もがんばります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む