at kaneshin

Free space for me.

Crystal で Markdown to HTML ツールを軽く書く

f:id:laplus-knsn:20161218190221p:plain

この記事は Crystal Advent Calendar 2016 の20日目の記事です。

普段は Golang を書いている事が多いので、 Golang の人になっていますが、暇を見つけては色々な言語にも触れています。今日はその一つである Crystal Language について軽く CLI ツールを作る方法を書こうと思います。

続きを読む

【高等数学】はさみうちの原理 〜関数の極限〜

この記事は 機械学習に必要な高校数学やり直しアドベントカレンダー Advent Calendar 2016 の20日目の記事です。

f:id:laplus-knsn:20161218204351p:plain

久しぶりに高校時代の教科書を引っ張ってきました。機械学習からはちょっとそれてしまいますが、級数について書きたいな〜と思っていたら懐かしい定理を見つけたので今回はその定理を選択しました。

続きを読む

Clojure と Leiningen と時々 Container 〜そしてデプロイへ〜

f:id:laplus-knsn:20161219001217p:plain

この記事は Clojure Advent Calendar 2016 の19日目の記事です。

Clojure を一度も使ったことが無かったのですが、少しは触ってみようと思い、今回 Clojure Advent Calendar 2016 に参加させてもらいました。

書くにあたり、凝ったことなんて全くできるはずもないので、自分らしい記事を書こうと思います。プログラミングには熟れており、これから初めて Clojure 触ってみる人の足がかりになるような記事になればと思います。

環境整備〜開発

とにかく、開発するなら Clojure を実行できなければダメだろうということで、Docker コンテナのイメージを使って楽に環境整備することに決めました。

Docker container

f:id:laplus-knsn:20161215020441p:plain

ここにあるコンテナイメージを pull して使用します。

$ docker pull clojure

タグでよく使うのは Description に記載されている通りすかね。

  • latest, lein-2.7.1
  • onbuild, lein-2.7.1-onbuild
  • alpine, lein-2.7.1-alpine
  • alpine-onbuild, lein-2.7.1-alpine-onbuild

コンテナイメージで不要なものが削減されたものを使用したければ alpine タグのを、実行するイメージなら onbuild タグのものを使用します。

まずは実行してみたいので、何も考えずに latest で取得して実行します。

$ docker pull clojure:latest
$ docke run -it -v ~/.clojure:/data-share clojure /bin/bash

これでインタラクティシェルでアタッチします。

Leiningen

アタッチしても Clojure の動かし方がわからなかったのですが、 /usr/local/bin に謎の lein というコマンドがあるので、「こいつが肝なんだろうな」とピンときました。

$ lein -h | head -n1
Leiningen is a tool for working with Clojure projects.

調べてみたところ、本当に色々やってくれるやつらしいので、こいつに乗っかることにします。

f:id:laplus-knsn:20161218104126p:plain



  < Clojure はワシが育てた。

さて、作業ディレクトリですが、私は Docker 起動時に /data-share というディレクトリをマウントしているので、そこで作業するとします。

まず、 lein new app でアプリケーションの雛形を作成します。

$ cd /data-share
$ lein new app hello-world

次に、 lein run で実行します。 勝手に色々とやってくれるので便利ですね。

$ cd hello-world
$ lein run
Retrieving org/clojure/clojure/1.8.0/clojure-1.8.0.pom from central
Retrieving org/sonatype/oss/oss-parent/7/oss-parent-7.pom from central
Retrieving org/clojure/tools.nrepl/0.2.12/tools.nrepl-0.2.12.pom from central
Retrieving org/clojure/pom.contrib/0.1.2/pom.contrib-0.1.2.pom from central
Retrieving clojure-complete/clojure-complete/0.2.4/clojure-complete-0.2.4.pom from clojars
Retrieving org/clojure/tools.nrepl/0.2.12/tools.nrepl-0.2.12.jar from central
Retrieving org/clojure/clojure/1.8.0/clojure-1.8.0.jar from central
Retrieving clojure-complete/clojure-complete/0.2.4/clojure-complete-0.2.4.jar from clojars
Hello, World!

もう Hello world 出ちゃいました。

REPL

Clojure でなく、 Leiningen でも REPL が起動できるんですね。なんかすごい。

$ lein repl
nREPL server started on port 59269 on host 127.0.0.1 - nrepl://127.0.0.1:59269
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.8.0
OpenJDK 64-Bit Server VM 1.8.0_111-8u111-b14-2~bpo8+1-b14
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

hello-world.core=> (-main)
Hello, World!
nil

完璧じゃないっすか…!と、いうことで、ここで終わりにしたいと思ったのですが、Clojure 使いの人から…

f:id:laplus-knsn:20161218103156p:plain




f:id:laplus-knsn:20161218103645p:plain



Webアプリケーションデプロイ とな。笑止。

duct on GKE

duct については紹介してもらった Qiita の記事を、GKE については自分の記事を参考にしています。

qiita.com

blog.kaneshin.co

duct

Leiningen にテンプレートがあるので、そちらから作成します。 +example は example というエンドポイントをリソースとして用意してくれるようです。

$ lein new duct myduct +example +cljs

ローカルの設定ファイルを生成するために、セットアップを行います。

$ lein setup

次に、サーバーを起動するために REPL を起動します。Clojure の REPL 文化は素晴らしいですね。

$ lein repl
nREPL server started on port 53589 on host 127.0.0.1 - nrepl://127.0.0.1:53589
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.8.0
OpenJDK 64-Bit Server VM 1.8.0_111-8u111-b14-2~bpo8+1-b14
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=> (dev)
:loaded
dev=> (go)
2016-12-18 01:59:12.911:INFO::nREPL-worker-0: Logging initialized @14796ms
Compiling "target/figwheel/myduct/public/js/main.js" from ["src" "dev"]...
Successfully compiled "target/figwheel/myduct/public/js/main.js" in 12.965 seconds.
notifying browser that file changed:  target/figwheel/myduct/public/js/goog/deps.js
notifying browser that file changed:  target/figwheel/myduct/public/js/cljs_deps.js
notifying browser that file changed:  out/cljs/user.js
:started
dev=> 

参考:Getting Started · duct-framework/duct Wiki · GitHub

起動したら、 curl などで叩いてみましょう。ポートは3000番で起動しています。

# execute command from outside.
# $ docker exec -it [Container Name] curl localhost:3000/example

$ curl localhost:3000/example
This is an example endpoint

REPL 上で (reset) すればリロードされるようです。 +cljs を付与しているので、 ClojureScript もここでコンパイルされるようです。(正直、よくわからない)

dev=> (reset)
:reloading (myduct.endpoint.example myduct.main myduct.endpoint.example-test cljs.stacktrace dev user)
:resumed
dev=>

エンドポイント追加

エンドポイントをひとつ追加してみます。

$ vim src/myduct/endpoint/hello.clj

ファイルの中身は example とほぼ同じですが、Hello 用にカスタマイズしました。

(ns myduct.endpoint.hello
  (:require [compojure.core :refer :all]))

(defn hello-endpoint [config]
  (context "/hello" []
    (GET "/" []
      "Hello, World!")))

次に、 system.edn ファイルにエンドポイントを定義します。

$ vim resources/myduct/system.edn

:endpoints で先ほどの Hello world を定義し、それを :app に登録します。

{:components
 {:app  #var duct.component.handler/handler-component
  :http #var ring.component.jetty/jetty-server}
 :endpoints
 {:example #var myduct.endpoint.example/example-endpoint
  :hello #var myduct.endpoint.hello/hello-endpoint}
 :dependencies
 {:http [:app]
  :app  [:example :hello]
  :example []}

ここまでできたら REPL でアプリを一度リセットします。

dev=> (reset)
:reloading (myduct.endpoint.hello)
Compiling "target/figwheel/myduct/public/js/main.js" from ["src" "dev"]...
Successfully compiled "target/figwheel/myduct/public/js/main.js" in 0.227 seconds.
:resumed

また curl などで /hello のパスを叩くと、Hello, World! が返却されると思います。

$ curl localhost:3000/hello
Hello, World!

duct をコンテナ化

ここまでできたらコンテナイメージとして作成し、Google Container Registry にプッシュします。

Dockerfile を duct プロジェクトのルートに置き、下記のように onbuild タグのコンテナイメージを使って docker build します。

FROM clojure:onbuild
# FROM clojure:alpine-onbuild

軽量にしたい場合は alpine-onbuild を使用してください。

# ビルド
$ docker build -t hello-duct .

# 実行
$ docker run -p 8080:3000 hello-duct

# 確認
$ curl -XGET localhost:8080/hello
Hello, World!

Google Container Registry にプッシュします。

# タグのエイリアス
$ docker tag hello-duct asia.gcr.io/[YOUR_PROJECT_ID]/hello-duct

# Registry にプッシュ
$ gcloud docker push asia.gcr.io/[YOUR_PROJECT_ID]/helloworld

ここまでできれば後は下記を参考にして GKE にデプロイすることが可能です。

blog.kaneshin.co

ClojureScript is 何者?

「まじでわからん。」という状態だったのですが、下記のスライド読んだら大体把握できたのと、「すごくね?」という感情が生まれました。

www.slideshare.net

GKE を使って golang アプリケーションコンテナを稼働させる

f:id:laplus-knsn:20161215015401p:plain

この記事は Google Cloud Platform(1) Advent Calendar 2016 の15日目の記事です。

普段は golang をやってるアプリケーションの人っぽい感じになっていますが、他にも色々とやっています。(と、いうアピールをたまにしておかないと) さて、今回は GKE + golang のデプロイ話でもしましょう。結局、golang が絡んでいますね。

準備

Docker をインストールしておき、使用できるようにしましょう。

コンテナイメージの作成・プッシュ

f:id:laplus-knsn:20161215020441p:plain

プロジェクト構成は下記のようにし、docker ビルドしたイメージを Container Registry にプッシュするようにします。

helloworld/
├── deployment.yaml
├── Dockerfile
├── main.go
└── service.yaml

main.go

アプリケーションコードはとてもシンプルに、8080番ポートで構えるサーバーを用意し、アクセスがあったときに "Hello world!" と表示するアプリケーションを用意します。

package main

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

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello world!")
    })

    log.Fatal(http.ListenAndServe(":8080", nil))
}

動作確認するには go run してビルド・起動し、 curl などで叩いてみてください。

# 実行
$ go run main.go

# 確認
$ curl -XGET localhost:8080
Hello world!

Dockerfile

golang をビルドするイメージが公開されているので、そのイメージを使用します。

FROM golang:1.7.4-onbuild

Docker ビルド・実行

main.go と Dockerfile が用意できたら実際にイメージをビルド・実行してみます。

# ビルド
$ docker build -t hello-container .

# 実行
$ docker run -p 8080:8080 hello-container

# 確認
$ curl -XGET localhost:8080
Hello world!

ビルドや実行で使用している hello-container はコンテナイメージへのタグです。

Container Registry にプッシュ

先ほど作成したコンテナイメージを Google Container Registry にプッシュします。先ほど作成したタグにエイリアスを張っておきます。

$ docker tag hello-container asia.gcr.io/[YOUR_PROJECT_ID]/helloworld

asia.gcr.io の部分はホスト先を指定することが可能です。

Registry Host Region
us.gcr.io US
es.gcr.io EU
asia.gcr.io ASIA
gcr.io US (unfixed)

あと、わざわざエイリアスを張らずに、直接レジストリの命名でタグを作成しても大丈夫です。 Google Container Registry 用にエイリアス・タグを作成したらプッシュします。

$ gcloud docker push asia.gcr.io/[YOUR_PROJECT_ID]/helloworld

[YOUR_PROJECT_ID] は GCP の Project ID を指定するようにしてください。


ここまで作成できたらコンテナイメージ作成については完了です。

Container Engine クラスタの作成

デプロイ先となるクラスタの作成を行います。コンソールから作成もできますし、コマンドからも作成が可能です。 お試しで試してみるなら Cloud Shell をオススメします。

f:id:laplus-knsn:20161215012952p:plain

クラスタ作成

今回は gcloud コマンドから作成します。

$ gcloud container clusters create helloworld --zone asia-northeast1-c \
  --scopes cloud-platform \
  --num-nodes 2

scopes オプションはアプリケーションの必要に応じて datastore や bigquery のスコープも --scopes cloud-platform,datastore,bigquery のように増やしてください。

f:id:laplus-knsn:20161215013439p:plain

クラスタの認証

gcloud コマンドで認証を行うために、作成したクラスタの認証から行います。

$ gcloud container clusters get-credentials helloworld --zone asia-northeast1-c

Fetching cluster endpoint and auth data. kubeconfig entry generated for helloworld.

が出力されればOK。

クラスタの確認

問題なく作成されたか確認してみます。

$ kubectl cluster-info
Kubernetes master is running at https://xxx.xxx.xxx.xxx
GLBCDefaultBackend is running at https://xxx.xxx.xxx.xxx/api/v1/proxy/namespaces/kube-system/services/default-http-backend
Heapster is running at https://xxx.xxx.xxx.xxx/api/v1/proxy/namespaces/kube-system/services/heapster
KubeDNS is running at https://xxx.xxx.xxx.xxx/api/v1/proxy/namespaces/kube-system/services/kube-dns
kubernetes-dashboard is running at https://xxx.xxx.xxx.xxx/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard

デプロイ

コンテナイメージ化されたアプリケーションをデプロイするためクラスタのレプリカセットとサービスの作成を行います。

Deployment

deployment.yaml という命名で下記のような Yaml ファイルを用意し、 kubectl コマンドを使用してレプリカセットを作成します。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: helloworld
  labels:
    app: helloworld
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
      - name: helloworld-app
        image: us.gcr.io/[YOUR_PROJECT_ID]/hello-golang:latest
        imagePullPolicy: Always
        ports:
        - name: http-server
          containerPort: 8080

continers.image には先ほどプッシュしたコンテナイメージの Registry Host を指定します。ここまでできたら kubectl コマンドを使用します。

$ kubectl create -f deployment.yaml 

レプリカセットの進捗状況は下記で確認がとれます。

$ kubectl get deployments
NAME         DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
helloworld   3         3         3            3           42m

Service

今度は service.yaml という命名で下記のような Yaml ファイルを用意します。サービスはレプリカセットと一緒に作成されたポッドにアクセスするための定義になります。

apiVersion: v1
kind: Service
metadata:
  name: helloworld
  labels:
    app: helloworld
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: http-server
  selector:
    app: helloworld
$ kubectl create -f service.yaml 

サービスの作成進捗は下記を叩くことでわかりますし、 LoadBalancer Ingress の IP が公開された IP アドレスとなるので、LoadBalancer 作成後、この IP にアクセスすることによって "Hello world!" が表示されるはずです。

$ kubectl describe service helloworld
Name:                   helloworld
Namespace:              default
Labels:                 app=helloworld
Selector:               app=helloworld
Type:                   LoadBalancer
IP:                     xxx.xxx.xxx.xxx
LoadBalancer Ingress:   xxx.xxx.xxx.xxx.
Port:                   <unset> 80/TCP
NodePort:               <unset> 31510/TCP
Endpoints:              xxx.xxx.xxx.xxx:8080,xxx.xxx.xxx.xxx:8080,xxx.xxx.xxx.xxx:8080
Session Affinity:       None
Events:
  FirstSeen     LastSeen        Count   From                    SubobjectPath   Type            Reason                 Message
  ---------     --------        -----   ----                    -------------   --------        ------                 -------
  43m           43m             1       {service-controller }                   Normal          CreatingLoadBalancer   Creating load balancer
  42m           42m             1       {service-controller }                   Normal          CreatedLoadBalancer    Created load balancer

動作検証

ここまでできたら、先ほどの LoadBalancer Ingress にある IP にアクセスして "Hello world!" が表示されることを確認してみましょう。

停止方法

クラスタを削除することによって、ポッドやロードバランサーもあわせて削除されます。超楽。

$ gcloud container clusters delete helloworld --zone asia-northeast1-c
The following clusters will be deleted.
 - [helloworld] in [asia-northeast1-c]

リソース