Green Software Engineer Blog

エンジニアブログです。技術情報(Go/Java/Docker)や趣味の話も書くかもです。

Serverless App作成ツール Up を利用してみた

概要

up と呼ばれるツールを利用してみました。
(Introductionをそのまま試してみただけです。)

github.com

upとは?

Serverless Applicationを簡単に作成、デブロイできるツールです。
現状の構築環境と方式は、下記のとおりです。

対応言語

個人的にはGolangに対応しているのが良いですね。
(Lambda自体はまだ、Golang対応していないので)

手順

こちらのサイト に記載のまんまです。

1. Install

curl直接 or npm

# curl版
% curl -sfL https://raw.githubusercontent.com/apex/up/master/install.sh | sh

# npm版 (内部で上記curlを叩いているだけです)
% npm i -g up

2. AWS Credenstialsの設定

~/.aws/credentials に追記が楽かと思います。

[go-up-test]
aws_access_key_id = xxxxxxxx
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxx

3. up.json(設定ファイル)作成

必須の項目は、profileのみです。
~/.aws/credentials で設定したprofile名と合わせてください。

{
    "profile": "go-up-test",
    "regions": ["ap-northeast-1"]
}

4. 好きな言語でサーバー側の処理を記載

package main

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

func main() {
        addr := ":" + os.Getenv("PORT")
        http.HandleFunc("/", hello)
        log.Fatal(http.ListenAndServe(addr, nil))
}

func hello(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello go-up-test")
}

5. Deploy

# develop環境へのDeploy
% up

# production環境へのDeploy
% up deploy production

6. 確認

# ブラウザでOpen
% up url --open

# ClipbordにCopy
% up url --copy
% curl "https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/development/"
Hello go-up-test

補足

  • up.json に諸々の設定を加えることができそうです
  • up logs コマンドでログが見れます
# past 5 minutes
% up logs

# real time log
% up logs -f
  • up stack delete で作成したresourcesを全て削除できます

サンプルコード

github.com

所感

AWSの設定さえ終わっていれば、非常に簡単にServerless Appを作成できると感じました。Slack Commandが簡単に作れるようなので、試してみたいです。 (元々、SlackのSlash Commandを簡単に作れるという記事を見かけて、興味を持ちました。)

参考

Machine Learning事始め (TensorFlow)

概要

お盆休みを利用して、Machine LearningをTensorFlowのTutorialを通して触れてみました。
解いた問題は、手書き数値の認識です。

Machine Learning

おこなっていることは、任意のグラフに対して近似する関数(=Model)を見つけること

用語

  • データ
    • (Training用) データとそのラベルのセットを持つ必要がある
      • ex) 手書き文字 <-> 書いてある文字
    • (Test用) accuracyを計測するためのデータ
      • 同様にデータと正解ラベルを持つ必要がある
  • weight
  • bias
    • weightと入力値の積をずらす
  • x
    • 入力値
  • y
    • 出力
  • activation function
evidence = W * x + b

-------------------
Tensor
W = weight
x = input
b = bias
-------------------
  • softmax function
    • evidence(活性化関数=activation functionの結果)を確率へと変換する
    • 確率より 0 <= y <= 1
  • loss function
    • 期待値と実際の結果の差分を計測する関数
    • ※ loss functionの結果を0に近づけることが目標
      (=期待値と実際の値が一致する)

Machine Learningの流れ

  1. Trainingデータセットを用意する
  2. activation function(W * x + b)を定義
  3. loss functionを定義
  4. TrainingするOptimizerを決めて、loss functionをセットする
    ex) Gradient Decent (最小勾配法)
  5. Trainingを実施
    テストデータを利用して、関数を何度も実行する。
    実行するたびに、WbをOptimizeする。(TensorFlow利用時は勝手にOptimizeしてくれる)
  6. Modelのaccuracyを測定する

サンプルコード

github.com

手書き数値の認識

CNNを利用せずにの実装もできましたが、accuracyを上げるためにも、CNNを利用したほうが良いみたいです。

Convolutional Neural Network

  • 畳み込みニューラルネットワーク
  • 面を一定の大きさのフィルタで覆い、領域で特徴量を抽出する方法
    • ex) 32x32の画像を5x5のフィルタでスライド1の場合 => 28x28の画像となる
    • pixel単位でないため画像が全体的に類似しているかを判断できる
  • Filter
    • パラメータ
      • filterの数
      • filterの大きさ
      • filterの移動幅
      • padding
        • 画像の端の領域を0で埋める
  • Layer
    • Convolutional Layer
    • Pooling Layer
      • サイズを圧縮する層
      • max poolingが利用される
        • 領域内の最大値を取る手法
    • Fully Connected Layer

肝となると感じた部分

  • 大量のTrainingデータを用意すること
  • accuracyを向上させるためのチューニング

まとめ

基本的に、TensorFlowのTutorialを上からなぞってコード書いてみただけですが、
Machine Learningで何やってるかを少しだけ知ることができました。
絶賛、下記を読み込み中なので読めたらneural networksdeep learningについて
正確に理解できるかなと思います。

Neural networks and deep learning

ひとまずPythonでやってみてますが、GolangでもTensorFlow自体は使えるので、
書き直したいところです。

参考

【WEB+DB PRESS Vol.99】k8sの記事での利用コマンド

概要

WEB+DB PRESS Vol.99 の記事を参考にk8sを利用してみました。
詳しい内容は、記事をご参照いただければと思います。(非常にわかりやすい記事でした)
下記は、記事に書いてあるコマンドのままですが、メモ書き程度に思っていただければ。

Amazon CAPTCHA

コマンド

gcloud側の設定

# project の設定
% gcloud config set project xxxx

# ゾーンの設定
% gcloud config set compute/zone asia-northeast1-a

# 認証設定
% gcloud auth login

コンテナクラスタ起動

# クラスタ起動
% gcloud container clusters create one \
--cluster-version=1.6.7 \
--machine-type=g1-small
Creating cluster one...done.
Created [https://container.googleapis.com/v1/projects/xxxxx/zones/asia-northeast1-a/clusters/one].
kubeconfig entry generated for one.
NAME  ZONE               MASTER_VERSION  MASTER_IP       MACHINE_TYPE  NODE_VERSION  NUM_NODES  STATUS
one   asia-northeast1-a  1.6.7           35.190.233.232  g1-small      1.6.7         3          RUNNING

GCRにイメージをbuildしてpush

# Container build file
% cat manifest/cloudbuild.yaml
  env: ['PROJECT_ROOT=one']
  args: ['build', '-o', 'goneup']
- name: 'gcr.io/cloud-builders/docker'
  env: ['PROJECT_ROOT=one']
  args: ['build', '--tag=asia.gcr.io/$PROJECT_ID/one/goneup', '.']
images: ['asia.gcr.io/$PROJECT_ID/one/goneup']%

# push gcr
% gcloud container builds submit --config=manifest/cloudbuild.yaml .

kubernetes利用

Pod単独

% cat manifest/pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: goneup
spec:
  containers:
    - image: asia.gcr.io/[project]/one/goneup:latest
      imagePullPolicy: Always
      name: goneup

# create pod
% kubectl create -f manifest/pod.yaml

# port forward
% kubectl port-forward [pod name] [local port]:[external port]

ReplicaSet

apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
  name: goneup
spec:
  replicas: 3
  template:
    metadata:
      labels:
        name: goneup
    spec:
      containers:
        - image: asia.gcr.io/[project]/one/goneup:latest
          imagePullPolicy: Always
          name: goneup

# create replicaset
% kubectl create -f manifest/replicaset.yaml

# check replicasets
% kubectl get replicasets
NAME                DESIRED   CURRENT   READY     AGE
goneup-1100937273   2         2         2         57s

# replace replicaset
% kubectl replace -f manifest/replicaset.yaml

Deployment

% cat manifest/deployment.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: goneup
spec:
  replicas: 3
  template:
    metadata:
      labels:
        name: goneup
    spec:
      containers:
        - image: asia.gcr.io/[project]/one/goneup:latest
          imagePullPolicy: Always
          name: goneup

# create deployment
% kubectl create -f manifest/deployment.yaml

# check deployment
% kubectl get deployment
NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
goneup    2         2         2            2           50s

Service

% cat manifest/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: goneup
spec:
  type: LoadBalancer
  selector:
      name: goneup
  ports:
      - port: 8080

# create service
% kubectl create -f manifest/service.yaml

Ingress

% cat manifest/ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: goneup
spec:
  rules:
    - http:
        paths:
          - path: /*
            backend:
              serviceName: goneup
              servicePort: 8080

# create ingress
% kubectl create -f manifest/ingress.yaml

まとめ

マニフェストファイルを作成することで、簡単にGCP上にk8sを構築できました。
また情報にアクセスしたい際は、kubectl get xxxで簡単にアクセスできるところが非常にいいなと感じました。
基本的な設定しか入れていないので、利用してみてもっと詳細な設定まで使いこなせると良いかと思います。

Dockerについて

概要

Dockerについて、本当に簡単な部分だけまとめてみました。
(ここから、Docker->ECS->k8s->GKEと進めていければなぁと思っています。)

Dockerとは

Software Container Platform=コンテナが動作するプラットフォーム

Containerとは

  • 一つSoftwareが実行されるために必要なすべてをパッケージングしたもの
  • Softwareを動かすために必要なライブラリと設定のみが入っている
  • Container内で、独自にリソースを持つ(メモリ/プロセス/ネットワーク)

どういった問題を解決するのか

「自分のローカルに環境で動いたけど、検証サーバーに載せると動かない..」
→ こういった、環境差分を解決することができる
Containerにくるむことで、どこでも同じように安全に動作することが保証されます。 そのため、Containerにくるんでおけば安心して、検証、本番環境へとSoftwareをリリースすることができます。

Container(= Docker Container)の作り方

Dockerfileを書きましょう。

Dockerfileとは

  • Docker Containerの設計が書かれたファイル
  • Dockerfile` というファイル名で作成する
  • 記述はDockerfile用DSL
  • Dockerエンジン + Dockerfile + ソースファイル があればどの環境でも同じように実行可能

Docker image

  • Dockerfileに記述されたOSやアプリケーションコード等をまとめたテンプレート
  • Containerはimageを実体化したもの
  • Dockerfileをbuildすることで作成することができる
  • Dockerエンジン + Docker image があればどの環境でも同じように実行可能
    • imageを作成して、Docker Hub等のコンテナ管理サービスにpushしておく
    • Docker Hub等からimageをpullしてくるだけで実行可能に

Docker Containerの生成手順

  1. Dockerfile記述
  2. BuildしてDocker imageの作成
  3. imageをもとにRunしてContainerの起動

例: Go Appの実行

下記のようなGolangHello WorldのコードをContainer内で実行してみます。

package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}

\1. Dockerfile記述
下記のようなDockerfileを作成します。(詳細はコメント参照)

# ベースイメージの指定
# Docker Hub(https://hub.docker.com/)上に様々なイメージが公開されている
# そちらから利用したいベースイメージを選択
FROM golang:1.8.3-alpine3.6

# 作業を行うディレクトリを選択
WORKDIR /go/src

# コマンドを実行
# golangイメージは Alpine Linuxをもとに作成(軽量のためDockerでよく利用される)
# linuxコマンド(ls,mkdir)等が実行可能
RUN mkdir hello

# 作業を行うディレクトリを変更
WORKDIR /go/src/hello

# 作業ディレクトリからコンテナ内にファイルをコピー
# COPY [ホスト側] [コンテナ内]
COPY . .

# Go Appをbuild
RUN go build -o hello main.go

# コンテナ起動時に実行されるコマンド
CMD ["/go/src/hello/hello"]

\2. BuildしてDocker imageの作成

% ls
Dockerfile      main.go

# imageの作成
# (-t でタグを設定できる)
% docker build -t hello:1.0 ./

# imageの確認
% docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello               1.0                 df9a562be589        10 minutes ago      259MB

\3. imageをもとにRunしてContainerの起動

# コンテナの実行
% docker run hello:1.0
Hello World

まとめ

docker-composeやswam等は別途まとめようかなと思います。 また、詳細なDockerfileのDSLや、docker build/runオプションについてもまとめられたら良いですね。

参考

エンジニアとして学びたいこと(歴2年目)

概要

エンジニアとして、どのあたりを学んでいこうかなぁとの備忘録

項目

Golang

最近ずっと利用しているメイン言語です。仕事上ではJavaを利用することが多いですが、 書いている時間も量もそろそろ超えてくるのではと思っています。

好きなところはこんな感じです。

  • ビルドしたバイナリがあればどの環境でも動かしやすい点
  • 型あり
  • Httpサーバーの起動しやすさ
  • プログラマーならたぶん誰でも読める
  • ドキュメントが読みやすい

学びたいこと

  • パッケージの構成方法
    • 自由に書けてしまってどこに何を書くかが明確になっていない
    • パッケージの切り方
    • 各ファイル内のinterface,strcut,methodの構成
  • 周辺ツールの利用方法
    • ツールが充実しているのことで、きっちり使い方を抑えたい(pprofとか)
  • 実務上でのコードレベル
    • OSSのライブラリ等を読んでみる等ですかね
  • 体系的な知識
    • Webでの情報がメインのため、体系的な知識が身についてない疑惑
    • 書籍でまとまったものがあれば一度読むのも良いかも

次のステップ

クラウド

実務できっちり利用できていない分、プライベートで学んでおかないと遅れが目立ってしまう印象です。

学びたいこと

  • GAE/Goの動作方法
  • k8sについて
  • GCP主要サービス理解
    • そもそものサービスについてあまり知らない
  • AWS(ざっくり)

次のステップ

低レイヤー

Goならわかるシステムプログラミングを読んでいて感じましたが、低レイヤーの知識が圧倒的に足りないと感じます。このあたりのコンピュータサイエンスについて、きっちり抑えておきたいです。
低レイヤー周りを最適化したコードを実装できるように、理解を深めたいです。

学びたいこと

  • Unixについて
  • プロセスについて
  • メモリ管理について(GC等)
  • 通信について

次のステップ

まとめ

ざっくりと思い当たることについて、並べて書いてみました。自分の整理にはなったかなとは思います。
学ぶのに良い情報をお持ちの方がいたら、ぜひ教えていただけると幸いです。
まだまだ、手を動かしてコードを書く経験値が足りなさすぎるので、手はとにかく日々動かし続けていきたいとは思っています。

GoSNS

概要

github.com

AmazonSNS likeな、簡易メッセージングサーバーを練習がてら書いてみました。
(Amazon SNSちゃんと使ったことないので、ぜんぜん違うかもしれないですが…)
モデルは、Pub/Subを意識しています。

機能概要

  • Channel登録
  • Channelに対して購読登録
    • 購読手段はSlackのWebHook一択(Mail等の対応も検討中)
  • 新規Channelの開設
  • Handshakeリクエス

簡易API Doc一覧

メルカリ製のgo-httpdocを利用してドキュメント生成しました。

github.com

I/F

基本POSTリクエストでサーバーとやり取りをします。
POSTのBodyに下記のRequest構造のJSONを書き込んでリクエストします。

Channel登録 (/meta/channel)

新規に開設したい、Channelを登録します。

Request

名前 概要
channel String 登録したいchannel名

サンプル

{
  "channel": "golang"
}

Response

JSON構造

名前 概要
channel String 登録したchannel名
successful String 登録成否
error
(optional)
String エラー

購読登録 (/meta/subscribe)

購読の登録をします。
現在は、Slack通知(WebHook URL)を用いての手法にのみ対応しております。

Request

名前 概要
channel String /meta/subscribe
client_id String ID(現在は適当な文字列)
subscriptions Array(String) 購読したいchannelのリスト
method Method 購読手法

Method

名前 概要
method String 購読手段(下記選択)
- slack
webhook_url String Slack WebHookURL (slack選択時必須)

サンプル

{
  "channel": "/meta/subscribe",
  "client_id": "MRAjWwhTHcgagka",
  "subscription" : [
    "/golang"
  ],
  "method" : {
    "format": "slack",
    "webhook_url": "https://hooks.slack.com/services/XXX"
  }
}

Response

JSON構造

名前 概要
channel String /meta/subscribe
successful String 購読成否
client_id String ID
subscriptions Array(String) 購読したchannelのリスト
error
(optional)
String エラー

Topic登録 (/topic)

Channelに対して、Topicを登録します。

Request

名前 概要
channel String Topic登録するchannel名
data String Topic内容
{
  "channel": "/golang",
  "data" : "*Update GAE Go1.8*"
}

Response

文字列

文字列
成功 ok
失敗 not found channel

内部実装

どのように実装をしているか、メモ書き程度に残しておきます。

Pub/Sub Model

PublisherとSubscriberの関係は、1Topicに限ると1対多の関係です。
PublisherはどのSubscriberに送信するかといった情報には関与しません。
Publisherはtopicを送るだけ、Subscriberは送られたTopicを購読するだけといった構成です。

データ保持

購読データの保持は、JSONファイルにて行っております。
(DB等の利用がベタ-かと思いましたが、ファイルが一番楽だったので選びました)
ファイルI/Oを呼び出しごとに発生させたくなかったため、内部でキャッシュを持たせています。 キャッシュとファイルは常に同期している想定です。
ただ、キャッシュから時間でデータが破棄されることもあり、キャッシュにない場合はファイルを見にいくようにしてます。

ユーザー認証

完全に未実装です。認証なくPOSTが送れたら自由にデータの書き換えができます。

テスト

go-httpdoc に記法にならって記述しています。
API I/Fの部分以外で書けていないところは、順次書き足したいです。

パッケージ管理

depを利用しています。 管理ファイルがtomlファイルになってて驚きましたが、問題なく使えています。

利用しているライブラリ一覧

感想

Goを触り始めて、初めてある程度ましな成果物を作成しました。
AmazonSNSのドキュメントを読んで、メッセージングサーバーの面白さを感じ、とりあえず自分でも作ってみたといった感じです。

書いてて感じたことですが、パッケージ構成の方法 がよくわからなかったです。
パッケージの切り方や、同一パッケージ内でのファイル分割の方法等で、いまいち方針がなく何度も再構成しました。
他の方のソースを読んで、このあたりは勉強していこうかなと思います。
総じて、楽しく書けたのでGoはかなり自分にあっている気がします。

sync.Poolの挙動について(Golang)

概要

標準パッケージのsync.Poolを利用して、ファイルの中身を常にメモリ上(=キャッシュ)におきたかったのですが、動作が不定のためうまく利用できなかった話です。

Go version

% go version
go version go1.8.1 darwin/amd64

sync.Pool概要

概要としては、以下の認識です

  • オブジェクトを一時的に、保持するための機能
  • 再利用されるオブジェクトをキャッシュする利用法
  • 複数のgoroutineで安全に利用可能
  • copy禁止(noCopystructが埋め込まれている)

メソッド

Get

func (p *Pool) Get() interface{}

  • Poolよりitemを取り出す
  • 取り出されたitemはPoolから削除
  • Putで入れた値とGetで取り出す値の関係を考慮すべきでない

Put

func (p *Pool) Put(x interface{})

  • Poolに値を追加する

動作確認

下記のコードを利用して、Getした際とPutした際の挙動を確認してみました。
https://github.com/midorigreen/gopool/blob/master/main.go

package main

import (
    "fmt"
    "net/http"
    "sync"
)

var pool = sync.Pool{
    New: func() interface{} {
        return "init"
    },
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/pool", poolHandler)
    mux.HandleFunc("/pool/change", poolChangeHandler)
    http.ListenAndServe(":8888", mux)
}

func poolHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("----------------------------------")
    fmt.Println("/pool")
    fmt.Println(get().(string))
    fmt.Println("----------------------------------")
}

func poolChangeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("----------------------------------")
    s := get().(string)
    fmt.Println("/pool/change")
    pool.Put(s)
    fmt.Println("-- changed --")
    fmt.Println("----------------------------------")
}

func get() interface{} {
    s := pool.Get().(string)
    cpS := s
  // Getした値を再度Poolに戻している
    pool.Put(s)
    return cpS
}

出力例

実際の出力例です。
上記コードより、get()で呼び出した際は、Get→Putとしているため
同じ値がGet()で取得されて欲しいところです。
ですが、一度/pool/changeAPIを叩いた後も"init"が出力されていることから、
Get→Putで値を戻したところで、必ず同じ値をGet()してくれるわけではなさそうです。

----------------------------------
/pool
init
----------------------------------
----------------------------------
/pool/change
-- changed --
----------------------------------
# ↓ これ以降はすべて "change handler" が出力されていて欲しい
----------------------------------
/pool
change handler
----------------------------------
----------------------------------
/pool
init
----------------------------------
----------------------------------
/pool
change handler
----------------------------------
----------------------------------
/pool
init
----------------------------------
----------------------------------
/pool
change handler
----------------------------------
----------------------------------
/pool
init
----------------------------------
----------------------------------
/pool
change handler
----------------------------------
----------------------------------
/pool
change handler
----------------------------------
----------------------------------
/pool
init
----------------------------------

感想

Callers should not assume any relation between values passed to Put and the values returned by Get.
→ Putで入れた値とGetで取り出す値の関係を考慮すべきでない

とあるように、Put()で入れたからGet()で必ず入れた値が取得できるわけではないことを念頭に置いて開発しないといけないようです。
ファイルの中身をキャッシュしておいて、書き換え時は更新等を行いたかったのですが、 更新をしたデータをPut()で突っ込んでも返ってくるとは限らなくなってます。
結論、あまり良い使い方ではなさそうです。(一意にキャッシュを保てないので、実データと差分がでてしまうため)

補足

@nobonoboさんにも指摘頂きました。

参考