20190507のdockerに関する記事は7件です。

SkaffoldのGetting StartedをMinikubeでサクッと試す

Cloud Native Online #01に登壇した際のスライドもご覧ください。

Skaffoldとは

Skaffold継続的なKubernetesアプリケーション開発を促進するツールで、
ソースコードの変更をトリガとして以下を自動で行ってくれることで、我々開発者はアプリケーションの開発に注力できるようになります。

  • コンテナイメージのビルド
  • コンテナレジストリへのpush
  • k8sクラスタへのデプロイ
    • kubectlの他、helmkustomizeも記述できる

画像はskaffold.dev/docsより引用
ソースコードの変更をトリガとしてk8sクラスタにデプロイされるまでの流れが示されています。
skaffold.png

Forerver(Node)RailsDjangoがソースコードの変更を検出してサーバを再起動する機能のKubernetes版と考えて良さそうです。

本エントリにはSkaffoldのGetting StartedをMinikubeで実施する場合に必要となる手順を記載します。

環境

Ubuntu 17.10Minikube v1.0.0を使用しています。

$ uname -a
Linux mina 4.13.0-46-generic #51-Ubuntu SMP Tue Jun 12 12:36:29 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=17.10
DISTRIB_CODENAME=artful
DISTRIB_DESCRIPTION="Ubuntu 17.10"
$ minikube version
minikube version: v1.0.0
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.0", GitCommit:"641856db18352033a0d96dbc99153fa3b27298e5", GitTreeState:"clean", BuildDate:"2019-03-25T15:53:57Z", GoVersion:"go1.12.1", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.0", GitCommit:"641856db18352033a0d96dbc99153fa3b27298e5", GitTreeState:"clean", BuildDate:"2019-03-25T15:45:25Z", GoVersion:"go1.12.1", Compiler:"gc", Platform:"linux/amd64"}
$

Minikubeの準備

MinikubeとSkaffoldを組み合わせて使用する場合、留意事項があります。

Skaffoldは、k8sクラスタがMinikubeであると認識した場合、
docker buildは行いますが、docker pushは行いません。

これは、docker buildコマンドが、MinikubeのVM内でのdocker buildとなるよう設定されていることが前提となっていると考えられます。

これには、minikubeのdocker-envコマンドを活用できます。
このコマンドは以下のように、ローカルのdockerコマンドをMinikubeのVM内のdockerに接続するための環境変数設定を表示してくれます。

$ minikube docker-env
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/home/loft/.minikube/certs"
export DOCKER_API_VERSION="1.35"
# Run this command to configure your shell:
# eval $(minikube docker-env)
$

以下のようにevalで実行すると環境変数が設定されます。

$ eval $(minikube docker-env)

以降、dockerコマンドはMinikubeのVM内のdockerに対する操作になることに留意する必要はありますが、
この機構によりコンテナイメージをわざわざレジストリにpushしなくても
minikubeにアプリケーションをデプロイできるので素早い開発サイクルを回せるメリットがあります。

これを行なっていない場合、dockerイメージはMinikubeのVMの外のローカルでビルドされるため、当然PodのStatusはImagePullBackOffとなります。

Skaffoldのインストール

installing-skaffoldを参照

例) Linuxの場合

$ curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64
$ chmod +x skaffold
$ sudo mv skaffold /usr/local/bin

Skaffold サンプルの入手

リポジトリをcloneし、examples/getting-startedディレクトリに移動しましょう。
構成物については後述します。

$ git clone https://github.com/GoogleContainerTools/skaffold
$ cd examples/getting-started
$ ls -Fla
total 28
drwxrwxr-x  2 loft loft 4096  5月  7 21:00 ./
drwxrwxr-x 19 loft loft 4096  5月  7 21:00 ../
-rw-rw-r--  1 loft loft  144  5月  7 21:00 Dockerfile
-rw-rw-r--  1 loft loft  153  5月  7 21:00 k8s-pod.yaml
-rw-rw-r--  1 loft loft  131  5月  7 21:00 main.go
-rw-rw-r--  1 loft loft  734  5月  7 21:00 README.adoc
-rw-rw-r--  1 loft loft  158  5月  7 21:00 skaffold.yaml
$

動作確認

ターミナルを2つ(terminal-1terminal-2)用意し、以下を実行します。

terminal-1

skaffold devコマンドを実行します。
以下が行われる様子を確認できます。

  • docker build ( MinikubeのVM内でbuild )
  • docker push ( Minikubeの場合は行わない )
    • Found [minikube] context, using local docker daemon.より、Minikubeを認識していることが分かる
  • kubectl apply
  • 標準出力ログの表示
  • ソースコードの変更の監視
$ skaffold dev
Generating tags...
 - gcr.io/k8s-skaffold/skaffold-example -> gcr.io/k8s-skaffold/skaffold-example:v0.28.0-89-gf25b09fa-dirty
Tags generated in 7.774075ms
Starting build...
Found [minikube] context, using local docker daemon.
Building [gcr.io/k8s-skaffold/skaffold-example]...
Sending build context to Docker daemon  3.072kB
Step 1/6 : FROM golang:1.10.1-alpine3.7 as builder
 ---> 52d894fca6d4
Step 2/6 : COPY main.go .
 ---> Using cache
 ---> 04e5f4a5d5e2
Step 3/6 : RUN go build -o /app main.go
 ---> Using cache
 ---> eb45bd0c0ce1
Step 4/6 : FROM alpine:3.7
 ---> 6d1ef012b567
Step 5/6 : CMD ["./app"]
 ---> Using cache
 ---> da76805ab2e2
Step 6/6 : COPY --from=builder /app .
 ---> Using cache
 ---> 9c5ffabae1d9
Successfully built 9c5ffabae1d9
Successfully tagged gcr.io/k8s-skaffold/skaffold-example:v0.28.0-89-gf25b09fa-dirty
Build complete in 72.162899ms
Starting test...
Test complete in 5.744µs
Starting deploy...
kubectl client version: 1.14
pod/getting-started created
Deploy complete in 301.514962ms
Watching for changes every 1s...
[getting-started] Hello world!
[getting-started] Hello world!
[getting-started] Hello world!
( 略 : )

terminal-2

アプリケーションのソースコードを書き換えてみましょう。

# 書き換え前
$ cat main.go
( 略 : )
        fmt.Println("Hello world!")
( 略 : )

# 書き換え
$ vim main.go

# 書き換え後
$ cat main.go
( 略 : )
        fmt.Println("Hello skaffold!")
( 略 : )

terminal-1

再びterminal-1を確認してみると、ソースコードの変更をトリガとして以下が行われる様子を確認できます。

  • docker build ( MinikubeのVM内でbuild )
  • docker push ( Minikubeの場合は行わない )
  • kubectl apply
  • 標準出力ログの表示
  • ソースコードの変更の監視

手作業でイメージをビルドし、レジストリにpushし、クラスタにデプロイする手間が省かれていますね。

( 略 : )
[getting-started] Hello world!
[getting-started] Hello world!
[getting-started] Hello world!
Generating tags...
 - gcr.io/k8s-skaffold/skaffold-example -> gcr.io/k8s-skaffold/skaffold-example:v0.28.0-89-gf25b09fa-dirty
Tags generated in 21.094584ms
Starting build...
Found [minikube] context, using local docker daemon.
Building [gcr.io/k8s-skaffold/skaffold-example]...
Sending build context to Docker daemon  3.072kB
Step 1/6 : FROM golang:1.10.1-alpine3.7 as builder
 ---> 52d894fca6d4
Step 2/6 : COPY main.go .
 ---> 4a61f315455a
Step 3/6 : RUN go build -o /app main.go
 ---> Running in 30ee9d0e606f
Removing intermediate container 30ee9d0e606f
 ---> 6a9d5e79631a
Step 4/6 : FROM alpine:3.7
 ---> 6d1ef012b567
Step 5/6 : CMD ["./app"]
 ---> Using cache
 ---> da76805ab2e2
Step 6/6 : COPY --from=builder /app .
 ---> 16dbf70b67d6
Successfully built 16dbf70b67d6
Successfully tagged gcr.io/k8s-skaffold/skaffold-example:v0.28.0-89-gf25b09fa-dirty
Build complete in 1.75830858s
Starting test...
Test complete in 11.741µs
Starting deploy...
kubectl client version: 1.14
pod/getting-started configured
Deploy complete in 296.352796ms
Watching for changes every 1s...
[getting-started] Hello skaffold!
[getting-started] Hello skaffold!
( 略 : )

構成物の確認

main.go

アプリケーションのソースコード。
1sec毎に標準出力にHello world!を表示する。

$ cat main.go
package main

import (
    "fmt"
    "time"
)

func main() {
    for {
        fmt.Println("Hello world!")
        time.Sleep(time.Second * 1)
    }
}

Dockerfile

main.goをビルドしコンテナ起動時のコマンドとしている。

$ cat Dockerfile
FROM golang:1.10.1-alpine3.7 as builder
COPY main.go .
RUN go build -o /app main.go

FROM alpine:3.7
CMD ["./app"]
COPY --from=builder /app .
$

k8s-pod.yaml

gcr.io/k8s-skaffold/skaffold-exampleというイメージを使用するPodの定義。

$ cat k8s-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: getting-started
spec:
  containers:
  - name: getting-started
    image: gcr.io/k8s-skaffold/skaffold-example
$

skaffold.yaml

Skaffoldの設定ファイル。

  • build設定
    • ビルドするイメージ名としてgcr.io/k8s-skaffold/skaffold-exampleを指定している
  • deploy設定
    • kubectlコマンドを使用している
    • k8s-で始まるマニフェストファイル(yaml)を指定している
$ cat skaffold.yaml
apiVersion: skaffold/v1beta9
kind: Config
build:
  artifacts:
  - image: gcr.io/k8s-skaffold/skaffold-example
deploy:
  kubectl:
    manifests:
      - k8s-*
$

まとめ

いかがでしたか?
Skaffoldを使うと、クラスタにデプロイされるまでのルーチンが自動化されるので効率良く開発できそうですね。
特にMinikubeを開発環境として使用している場合は恩恵が大きそうです。

今回はSkaffoldのさわりであるGetting Startedを試してみましたが、Tutorialsも用意されています。
本記事がSkaffold導入の参考になりましたら幸いです。

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

一足遅れて Kubernetes を学び始める - 08. discovery&LB その1 -

ストーリー

  1. 一足遅れて Kubernetes を学び始める - 01. 環境選択編 -
  2. 一足遅れて Kubernetes を学び始める - 02. Docker For Mac -
  3. 一足遅れて Kubernetes を学び始める - 03. Raspberry Pi -
  4. 一足遅れて Kubernetes を学び始める - 04. kubectl -
  5. 一足遅れて Kubernetes を学び始める - 05. workloads その1 -
  6. 一足遅れて Kubernetes を学び始める - 06. workloads その2 -
  7. 一足遅れて Kubernetes を学び始める - 07. workloads その3 -
  8. 一足遅れて Kubernetes を学び始める - 08. discovery&LB その1 -

前回

一足遅れて Kubernetes を学び始める - 07. workloads その3 -でようやくworkloadsが終了しました。今回は、discovery&LBを進めようと思います。

discovery&LB

Kubernetesには、下記のようにリソースの種類が存在します。
今回は、discovery&LBを学習します。

リソースの分類 内容
Workloadsリソース コンテナの実行に関するリソース
Discovery&LBリソース コンテナを外部公開するようなエンドポイントを提供するリソース
Config&Storageリソース 設定・機密情報・永続化ボリュームなどに関するリソース
Clusterリソース セキュリティやクォータなどに関するリソース
Metadataリソース リソースを操作する系統のリソース

KubernetesのWorkloadsリソース(その1)

discovery&LBをには、下記8つの種類があります。

  • Service
    • ClusterIP
    • ExternalIP
    • NodePort
    • LoadBalancer
    • Headless (None)
    • ExternalName
    • None-Selector
  • Ingress

Serviceの概要について学びます。

Kubernetesとネットワーク

Kubernetesでは、Pod毎にIPアドレスが割り振られています。
そのため、異なるPod間で通信する際は、PodのIPアドレスが必要になります。逆に同一のPod内ならlocalhostで通信できます。

説明するために、準備します。

sample-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80
        - name: redis-container
          image: redis:3.2
pi@raspi001:~/tmp $ k apply -f sample-deployment.yaml
pi@raspi001:~/tmp $ k get pods -l app=sample-app -o custom-columns="NAME:{metadata.name}, IP:{status.podIP},NODE:{spec.nodeName}"
NAME                                 IP           NODE
sample-deployment-9dc487867-h7lww   10.244.1.72   raspi002
sample-deployment-9dc487867-n8x5w   10.244.2.66   raspi003
sample-deployment-9dc487867-nxbxc   10.244.2.67   raspi003

Kubernetes_learning-Page-2 (1).png

このような状況下で、sample-deployment-9dc487867-n8x5w:redisを中心に見ていきます。

※ nginxは80ポートで開放されています。

前準備

pi@raspi001:~/tmp $ k exec -it sample-deployment-9dc487867-n8x5w -c redis-container /bin/bash
root@sample-deployment-9dc487867-n8x5w:/data# apt-get update && apt-get install curl -y
root@sample-deployment-9dc487867-n8x5w:/data# exit

curlがないのでインストールします。

同一Node,同一Pod内のコンテナへ通信

pi@raspi001:~/tmp $ k exec -it sample-deployment-9dc487867-n8x5w -c redis-container /bin/bash
root@sample-deployment-9dc487867-n8x5w:/data# curl localhost:80
<!DOCTYPE html>
...

OK

同一Node,異なるPodのコンテナへ通信

pi@raspi001:~/tmp $ k exec -it sample-deployment-9dc487867-n8x5w -c redis-container /bin/bash
root@sample-deployment-9dc487867-n8x5w:/data# curl 10.244.2.66:80
<!DOCTYPE html>
...
root@sample-deployment-9dc487867-n8x5w:/data# curl 10.244.2.67:80
<!DOCTYPE html>
...

OK

異なるNode,異なるPodのコンテナへ通信

pi@raspi001:~/tmp $ k exec -it sample-deployment-9dc487867-n8x5w -c redis-container /bin/bash
root@sample-deployment-9dc487867-n8x5w:/data# curl 10.244.1.72:80
<!DOCTYPE html>
...

OK

MasterNodeから各Podへ通信

pi@raspi001:~/tmp $ curl 10.244.1.72:80
<!DOCTYPE html>
...
pi@raspi001:~/tmp $ curl 10.244.2.66:80
<!DOCTYPE html>
...
pi@raspi001:~/tmp $ curl 10.244.2.67:80
<!DOCTYPE html>
...

OK

ここから分かるように、Pod内部の通信、Pod間の通信、さらにNode間の通信までも、Kubernetesによってネットワークが構築されています。

Service

Serviceは、下記の2つの大きな機能が存在します。

  • pod宛トラフィックのロードバランシング
  • サービスディスカバリとクラスタ内DNS

pod宛トラフィックのロードバランシング

先程の例で、Pod間を通信することは可能です。しかし、podを作り直すたびにIPアドレスが変わってしまうため、
自作すると、少し大変です。そこで、Serviceの出番です。
サービスは、複数存在するPodに対して自動的にロードバランスしてくれるのと、合わせて外向けのIPアドレス(ExternalIP)や、内向けのIPアドレス(ClusterIP)も提供してくれます。

さっそく、試してみます。

sample-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-clusterip
spec:
  type: ClusterIP
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8080
      targetPort: 80
  selector:
    app: sample-app

これは、app=sample-appにマッチするPodに対してロードバランスしてくれます。外から8080ポートで待ち受けて、80ポートでコンテナへ通信します。
spec.typeがClusterIPなので、内向けのIPアドレスが提供されています。

pi@raspi001:~/tmp $ k apply -f sample-clusterip.yaml
pi@raspi001:~/tmp $ k get service sample-clusterip
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
sample-clusterip   ClusterIP   10.111.197.69   <none>        8080/TCP   30s
pi@raspi001:~/tmp $ k describe service sample-clusterip
Name:              sample-clusterip
...
Selector:          app=sample-app
Type:              ClusterIP
IP:                10.111.197.69
Port:              http-port  8080/TCP
TargetPort:        80/TCP
Endpoints:         10.244.1.72:80,10.244.2.66:80,10.244.2.67:80
...

内向けに10.111.197.69のIPアドレスが振られました。また、ロードバランスする対象Podは、先にあげたPodのIPアドレスです。
Endpintsに:80とあるように、port毎にサービス(clusterIP)を作ることもできます。(serviceのspec.portsは配列指定)

アクセスできるのか、試します。
せっかくなので、pod毎にindex.htmlの内容を変化させましょう。

pi@raspi001:~/tmp $ for PODNAME in `k get pods -l app=sample-app -o jsonpath='{.items[*].metadata.name}'`; do
> k exec -it ${PODNAME} -- cp /etc/hostname /usr/share/nginx/html/index.html;
> done
pi@raspi001:~/tmp $ curl 10.111.197.69:8080
sample-deployment-9dc487867-nxbxc
pi@raspi001:~/tmp $ curl 10.111.197.69:8080
sample-deployment-9dc487867-n8x5w
pi@raspi001:~/tmp $ curl 10.111.197.69:8080
sample-deployment-9dc487867-h7lww

確かに、ロードバランシングによってpodに適度なランダム具合でアクセスできています。
もちろん、外からはアクセスできません。

iMacへ移動

~ $ curl 10.111.197.69:8080
# 返答なし

サービスディスカバリとクラスタ内DNS

サービスディスカバリとは、「問題においての解決策」を指しています。
Kubernetesにおける問題とは、動的にサービスが生成され続けていることによるサービスを特定することが難しくなる問題です。
そのサービスディスカバリが、Serviceにあります。
その方法について下記があります。

  • 環境変数を利用したサービスディスカバリ
    • PodにIPアドレスやport,protocolが設定されている。
  • DNS Aレコードを利用したサービスディスカバリ
    • Kubernetes内のクラスタ内DNSによって、ドメイン名によるアクセスができる。(ドメイン名の命名規則に従う)
  • DNS SRVレコードを利用したサービスディスカバリ
    • IPアドレスからドメイン名を取得する逆引きもできる。

dnsPolicyによる明示的な設定がない限り、Pod生成時にクラスタ内DNSへレコード追加されます。
クラスタ内DNSで名前解決できなかった場合は、クラスタ外DNSに問い合わせします。

お片付け

pi@raspi001:~/tmp $ k delete -f sample-deployment.yaml -f sample-clusterip.yaml

最後に

今回は、Serviceについての概要を学びました。Kubernetesの世界では、自動的にネットワーク構築されているため、特段意識することはありませんでした。
もう少し理解が進めれば、ネットワークがどのように構築されているのか、クラスタ内DNSがどのように動いているのか知りたいと思います。

※ お絵かきしてアウトプットすると、理解が深まるのでおすすめです。

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

Rails+Docker+HerokuでCI/CD

CI/CD初心者が、CI/CDを構築した時のメモ。
初めて触ったので、誤っているところがあるかもしれません。
ご指摘いただければ、幸いです。
以下、参考にした記事等です。
https://qiita.com/kei_f_1996/items/934296e23b0d8d877ff1
https://qiita.com/Kesin11/items/47079bc7f659e71b694c
https://docs.docker.com/compose/rails/

環境

OS:mac
Ruby:2.5.0
Rails:5.2.2
CicleCI
Heroku

手順

Dockerfileの作成
docker-compose.ymlの作成
database.yml
CicleCI
Heroku

Dockerfileの作成

基本的にはdocker docsのQuickstartを参考に書きました。

Dockerfile
FROM ruby:2.5.0
RUN apt-get update -qq && apt-get install -y postgresql-client sudo
RUN curl -sL https://deb.nodesource.com/setup_11.x | sudo -E bash -
RUN sudo apt-get install -y nodejs
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

CMD ["rails", "server", "-b", "0.0.0.0"]

3行目で、nodejsのversionを指定しています。
server.pidファイルが存在する場合に、railsのサーバが再起動しなくなったので、Quickstartに書かれている、
# Add a script to be executed every time the container starts.
の部分を書いておくべきでした。

docker-compose.ymlの作成

こちらもQuickstartを基に書いてます。

docker-compose.yml
version: '3'
services:
  db:
    image: postgres:9.6
    ports:
      - '5432:5432'
    volumes:
      - postgresql-data:/var/lib/postgresql/data
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0' 
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
      - chrome
  chrome:
    image: selenium/standalone-chrome:3.141.59-dubnium
    ports:
      - 4444:4444
volumes:
  postgresql-data:
    driver: local

・自動テスト時に、chromeでのテストがうまくできていなかったみたいなので、chrome部分を追加しました。
(対処として最適かどうかはわかりません)
・コンテナを潰してもDBのデータが消えないようにするために、volumesの部分を追加。

database.yml

database.ymlに記述を追加。
これもQuickstartを参考にしました。

database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  #追加
  host: db
  username: postgres
  password:
  pool: 5

あとはbuildしたりupしたりします。

$ docker-compose build
$ docker-compose up
$ docker-compose run web bin/rails db:create

CicleCI

アプリケーションのルートディレクトリに、.cicleciフォルダを作ります。
.cicleci直下に以下のconfig.ymlを記述します。

config.yml
version: 2
jobs:
  build:
    machine:
      image: circleci/classic:edge
    steps:
      - checkout
      - run:
          name: docker-compose build
          command: docker-compose build
      - run:
          name: docker-compose up
          command: docker-compose up -d
      - run:
          name: sleep for waiting launch db
          command: sleep 1
      - run:
          name: "before_test: setup db"
          command: docker-compose run web rails db:create db:migrate
      - run:
          name: test
          command: docker-compose run web bundle exec rspec
      - run:
          name: docker-compose down
          command: docker-compose down
  deploy:
    machine:
      image: circleci/classic:edge
    steps:
      - checkout
      - run:
          name: "build docker image"
          command: docker build --rm=false -t registry.heroku.com/${HEROKU_APP_NAME}/web .
      - run:
          name: setup heroku command
          command: bash .circleci/setup_heroku.sh
      - run:
          name: heroku maintenance on
          command: heroku maintenance:on --app ${HEROKU_APP_NAME}
      - run:
          # HEROKU_AUTH_TOKEN is generated by `heroku auth:token`
          name: "push container to registry.heroku.com"
          command: |
            docker login --username=_ --password=$HEROKU_AUTH_TOKEN registry.heroku.com
            docker push registry.heroku.com/${HEROKU_APP_NAME}/web
            bash .circleci/heroku-container-release.sh
      - run:
          name: heroku db migrate
          command: heroku run rails db:migrate --app ${HEROKU_APP_NAME}
      - run:
          name: heroku maintenance off
          command: heroku maintenance:off --app ${HEROKU_APP_NAME}
workflows:
  version: 2
  build_and_deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: master

https://circleci.com/docs/2.0/configuration-reference/

machine: の部分でdocker-composeをそのまま使えるように設定しています。
CicleCIのEnvironmentValiablesに、
HEROKU_APP_NAME,HEROKU_AUTH_TOKEN,HEROKU_LOGIN
,HEROKU_API_KEYを設定しておきます。

デプロイ時に実行するshellは以下の二つです。

setup_heroku.sh
wget https://cli-assets.heroku.com/branches/stable/heroku-linux-amd64.tar.gz
sudo mkdir -p /usr/local/lib /usr/local/bin
sudo tar -xvzf heroku-linux-amd64.tar.gz -C /usr/local/lib
sudo ln -s /usr/local/lib/heroku/bin/heroku /usr/local/bin/heroku

cat > ~/.netrc << EOF
machine api.heroku.com
  login $HEROKU_LOGIN
  password $HEROKU_API_KEY
EOF
# machine git.heroku.com
#   login $HEROKU_LOGIN
#   password $HEROKU_API_KEY

# Add heroku.com to the list of known hosts
ssh-keyscan -H heroku.com >> ~/.ssh/known_host
heroku-container-release.sh
#!/bin/bash

imageId=$(docker inspect registry.heroku.com/${HEROKU_APP_NAME}/web:latest --format={{.Id}})
payload='{"updates":[{"type":"web","docker_image":"'"$imageId"'"}]}'
curl -n -X PATCH https://api.heroku.com/apps/${HEROKU_APP_NAME}/formation \
-d "$payload" \
-H "Content-Type: application/json" \
-H "Accept: application/vnd.heroku+json; version=3.docker-releases" \
-H "Authorization: Bearer ${HEROKU_API_KEY}"

Heroku

postgresのアドオンを入れ忘れないように気をつけて、herokuの設定も完了すれば、CICDできます。たぶん

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

Remix IDEをDockerで起動してローカルディレクトリと同期する

Dockerでremix-ideをローカルディレクトリと同期してみる

WindowsでRemix IDEをインストールしようとしたところ、色々エラーが発生して手間だったのでDockerで環境を作ってみました。

環境

  • windows10
  • Docker

やってみる

remix-ide githubを参考に実施していきます。

1.コンテナの作成

nodeの公式イメージでコンテナの作成をしていきます。
パラメータに、remix-ideで利用するポートと、ファイルを編集しやすいように共有ディレクトリを設定しました。

$ docker run -it -d --name remix -p 8080:8080 -p 65520:65520 -v //E/work/share/Ethreum/remix-ide:/apl node:11.14.0
$ docker exec -it remix /bin/bash

2.remix-ideのリポジトリを取得

remix-ideをインストールしていきます。
インストール方法はnodeモジュールとしての取得とリポジトリのクローンどちらでもできるようですが、今回はリポジトリのクローンで実施していきます。

$ cd apl
$ git clone https://github.com/ethereum/remix-ide.git
$ cd remix-ide
$ npm update
$ npm install

3.remix-ideのビルド

公式のドキュメントに記載されている通り「npm start」と、実行していきたいですが、
このままでは以下のエラーが発生し動かないのでbuildします。

err
GET http://localhost:8080/build/app.js net::ERR_ABORTED 404 (Not Found)

ビルド。

$ npm run-script build

4.ローカルディレクトリ同期のための設定変更

次にローカルディレクトリ用の設定変更します。
この時点で「npm start」をすればremixは動作すると思いますが、ローカルディレクトリとの共有を行おうとすると以下のエラーが発生します。

err
WebSocket connection to 'ws://localhost:65520/' failed: Connection closed before receiving a handshake response
remix起動時ログ
[remixd  ] [WARN] You may now only use IDE at http://127.0.0.1:8080 to connect to that instance
[remixd  ] [WARN] Any application that runs on your computer can potentially read from and write to all files in the directory.
[remixd  ] [WARN] Symbolinc links are not forwarded to Remix IDE
[remixd  ] 
[remixd  ] [WARN] Symbolic link modification not allowed : ./contracts | /apl/remix-ide/contracts
[remixd  ] Sat Apr 27 2019 06:34:17 GMT+0000 (Coordinated Universal Time) Remixd is listening on 127.0.0.1:65520

ローカルディレクトリ同期用のリスナーが(127.0.0.1:65520)で立ち上がっているため、接続に失敗しているのだと思うので、IPを変更していきます。
websocket.js内のIPを「127.0.0.1」から「0.0.0.0」に変更します。
変更することで、ディレクトリ共有のリスナーが「0.0.0.0:65520」に変更されます。

$ sed -i s/127.0.0.1/0.0.0.0/g ./node_modules/remixd/src/websocket.js

動作確認

準備完了です!起動していきます!

npm start

> remix-ide@0.7.5 start /apl/remix-ide
> npm-run-all -lpr serve watch onchange remixd

[serve   ] 
[serve   ] > remix-ide@0.7.5 serve /apl/remix-ide
[serve   ] > execr --silent http-server .
[serve   ] 
[onchange] 
[onchange] > remix-ide@0.7.5 onchange /apl/remix-ide
[onchange] > onchange build/app.js -- npm-run-all lint
[onchange] 
[watch   ] 
[watch   ] > remix-ide@0.7.5 watch /apl/remix-ide
[watch   ] > watchify src/index.js -dv -p browserify-reload -o build/app.js --exclude solc
[watch   ] 
[remixd  ] 
[remixd  ] > remix-ide@0.7.5 remixd /apl/remix-ide
[remixd  ] > ./node_modules/remixd/bin/remixd -s ./contracts --remix-ide http://127.0.0.1:8080
[remixd  ] 
[watch   ] WS server listening on  39167
[remixd  ] [WARN] You may now only use IDE at http://127.0.0.1:8080 to connect to that instance
[remixd  ] [WARN] Any application that runs on your computer can potentially read from and write to all files in the directory.
[remixd  ] [WARN] Symbolinc links are not forwarded to Remix IDE
[remixd  ] 
[remixd  ] [WARN] Symbolic link modification not allowed : ./contracts | /apl/remix-ide/contracts
[remixd  ] Thu Apr 25 2019 11:46:17 GMT+0000 (Coordinated Universal Time) Remixd is listening on 0.0.0.0:65520
[remixd  ] Thu Apr 25 2019 11:46:26 GMT+0000 (Coordinated Universal Time) Connection accepted.
[remixd  ] setup notifications for /apl/remix-ide/contracts
[remixd  ] Thu Apr 25 2019 11:46:32 GMT+0000 (Coordinated Universal Time) Remix 172.17.0.1 disconnected.
[watch   ] NOW ASKING FOR CLIENT TO RELOAD
[watch   ] 26856676 bytes written to build/app.js (20.88 seconds) at 11:46:35 AM
[onchange] 
[onchange] > remix-ide@0.7.5 lint /apl/remix-ide
[onchange] > standard | notify-error
[onchange] 

ブラウザで「 http://127.0.0.1:8080 」を表示。
Remixの画面が表示されました!

image.png

次にローカルディレクトリと共有してみます。

image.png

サイドバーに「lockalhost」ディレクトリが表示されました!
ディレクトリ共有出来ているみたいです。

image.png

Remixのローカルディレクトリ同期先を変更したい場合は以下の「./contracts」のパスを変更すれば好きなディレクトリと同期できるようです。

package.json
"remixd": "./node_modules/remixd/bin/remixd -s ./contracts --remix-ide http://127.0.0.1:8080",

おまけ

Dockerfileも作ってみました。
/apl/remix-ide 配下でnpm startで動きます。

Dockerfile
FROM node:11.14.0
SHELL ["/bin/bash", "-c"]

RUN mkdir apl
WORKDIR  /apl

# remix-ide clone
RUN git clone https://github.com/ethereum/remix-ide.git

WORKDIR  /apl/remix-ide
RUN npm update
RUN npm install

# ホストで利用できるようIP変更
RUN sed -i s/127.0.0.1/0.0.0.0/g ./node_modules/remixd/src/websocket.js

# ビルド
RUN npm run-script build

参考

https://github.com/4c0n/remixd-docker/blob/master/Dockerfile

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

Docker Composeで起動したコンテナのログを、Amazon CloudWatch Logsに送る

TL;DR

  • Docker Composeで起動したコンテナのログを、Amazon CloudWatch Logsに送るようにしたい
    • 要するに、AWS環境で起動したコンテナのログを集約したい
    • AWS上のコンテナサービスの話は、ここでは置いておく
  • Dockerのlogging driverでAmazon CloudWatch Logs用のドライバがあるので、それを使用するようにdocker-compose.ymlに設定する

Dockerのlogging driver

Dockerコンテナには、ログをどこに出力するかを設定できる、logging driverの仕組みがあります。

Configure logging drivers

デフォルトは、json-file logging driverで、これはホスト側の/var/lib/docker/containers/[コンテナID]/[コンテナID]-json.logに出力されます。

JSON File logging driver

Dockerコンテナのログを、Amazon CloudWatch Logsに出力する

Dockerコンテナが使うlogging driverを、Amazon CloudWatch Logs logging driverに変更することで、DockerコンテナのログをAmazon CloudWatch Logsに送信することができます。
※なお、Amazon CloudWatch Logsにログ出力できるように、EC2へのIAMロールの付与は必要になります

Amazon CloudWatch Logs logging driver

あとは、これをdocker-compose.ymlに設定すればOKです。

Compose file reference / logging

以下のように、services内の各コンテナに対してlogging設定を書いていきます。指定するdriverは、awslogsです。

services:
  xxx:
    ...

    logging:
      driver: awslogs
      options:
        awslogs-region: ap-northeast-1
        awslogs-group: my-log-group

少なくとも、リージョン(awslogs-region)とロググループ(awslogs-group)は指定する必要があります。

また、あらかじめロググループは作成しておく必要があります

ログストリームは、デフォルトではコンテナのID(先頭12桁)が利用されます。

これを変更する場合は、awslogs-streamtagで指定して変更するとよいでしょう。

以下では、tagを使用して「イメージ名.コンテナ名.コンテナID(フル)」がログストリームとなるようにしています。

    logging:
      driver: awslogs
      options:
        awslogs-region: ap-northeast-1
        awslogs-group: my-log-group
        tag: "{{.ImageName}}.{{.Name}}.{{.FullID}}"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RDBMSにGTFSデータを格納してみる(環境作成編)

GTFS Advent Calendar 2日目の記事です。
今日はGTFSデータをデータベース(RDBMS)に格納するための手順を紹介しようと思います。

GTFSデータとRDBMS

GTFS(General Transit Feed Specification)は零細事業者の利用を視野に入れており、テキストエディタや表計算ソフトでの閲覧が容易なCSV形式を採用しています。具体的には経路情報(routes.txt)や便情報(trips.txt)のような情報の単位でCSVファイルが分かれています。

CSVファイル内のフィールドには他のCSVファイルのフィールドと関連付く項目があり、RDBMSにおける表結合の操作と相性が良さそうです。

というワケで、実際にGTFSデータをRDBMSに格納してSQLを介してGTFSデータを処理してみましょう。

RDBMS環境の準備

まずはRDBMS環境の準備です。Dockerを使用して手早く用意しましょう。
Dockerイメージ作成用のDockerfileは以下のリポジトリに用意してあります。

$ git clone https://github.com/ValLaboratory/advcal.git
$ cd advcal/2019/gw/docker_env/
$ docker-compose build gtfs_db

git cloneしたのち docker-compose build を実行します。

$ docker images
REPOSITORY  TAG     IMAGE ID      CREATED         SIZE
gtfs_db     latest  eab624c5d7f2  38 minutes ago  449MB

Dockerイメージが作成されるので、イメージからコンテナを起動します。

$ docker run --name mysql01 -e MYSQL_ROOT_PASSWORD=mysql -d -p 3306:3306 gtfs_db
$ docker exec -ti mysql01 mysql -uroot -p

上記の docker exec を実行すると、MySQLのコンソールにログインできます。
コンソール上から以下の手順を実行し、必要なデータベーススキーマとテーブルの作成、GTFSデータの投入を行います。
(必要なSQLファイルはDockerイメージ作成時にコンテナにコピーしています)

mysql> -- データベーススキーマを作成する。
mysql> source /tmp/setup_gtfs_db.sql
mysql>
mysql> -- ER図作成用のデータベースを作成し、テーブルを作成する。
mysql> use gtfs_db_reference
mysql> source /tmp/gtfs_reference.sql
mysql>
mysql> -- GTFSデータ格納用のデータベースを作成し、テーブルを作成する。
mysql> use gtfs_db
mysql> source /tmp/gtfs.sql
mysql> source /tmp/kitaena_gtfs_data.sql

これでRDBMSの準備は完了です。ER図作成用のデータベースは後のAdvent Calendarで解説します。
まずはGTFSデータ格納用のデータベースを触ってみます。

$ docker exec -ti mysql01 mysql -uroot -p
Enter password:
...中略...
mysql>
mysql> -- GTFSデータ格納用のデータベースに切り替えます。
mysql> use gtfs_db ;
mysql>
mysql> -- 適当なテーブルの定義を表示させてみます。
mysql> desc feed_info ;
+---------------------+-------------+------+-----+---------+-------+
| Field               | Type        | Null | Key | Default | Extra |
+---------------------+-------------+------+-----+---------+-------+
| feed_publisher_name | text        | NO   |     | NULL    |       |
| feed_publisher_url  | text        | NO   |     | NULL    |       |
| feed_lang           | varchar(16) | NO   |     | NULL    |       |
| feed_start_date     | varchar(12) | YES  |     | NULL    |       |
| feed_end_date       | varchar(12) | YES  |     | NULL    |       |
| feed_version        | text        | YES  |     | NULL    |       |
+---------------------+-------------+------+-----+---------+-------+
6 rows in set (0.00 sec)

mysql>
mysql> -- データを見てみる。
mysql> SELECT * FROM feed_info ;
+-----------------------------+---------------------------+-----------+-----------------+---------------+-------------------------------------------------+
| feed_publisher_name         | feed_publisher_url        | feed_lang | feed_start_date | feed_end_date | feed_version                                    |
+-----------------------------+---------------------------+-----------+-----------------+---------------+-------------------------------------------------+
| 北恵那交通株式会社          | http://www.kitaena.co.jp/ | ja        | 20190401        | 20200331      | 20190401_2019年04月01日(北恵那バス)           |
+-----------------------------+---------------------------+-----------+-----------------+---------------+-------------------------------------------------+
1 row in set (0.00 sec)

GTFSの各CSVファイル名をテーブル名、CSV内の各項目をテーブルのフィールドに対応させる形のテーブル定義となっています。そして、GTFSのデータとして北恵那交通株式会社GTFSデータを格納しています。

これでGTFSを格納したDBが用意できました。

まとめ

CSVの形で提供されているGTFSデータをRDBMSに格納するための環境作成を行いました。
次回以降のAdvent Calendarでは、このDBを使ってGTFSデータの処理をいろいろ試してみようと思います。

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

Arduino(ESP32)でROSを動かす

はじめに

Arduino (ESP32-WROOM-32; ESP32) + ROSでchatterサンプルを動かすところまでを構築した。
その実績メモを残す。

ググれば類似記事はたくさん出てくるので差別化のため本記事の特徴を書いておく。

  • ROSのバージョンがMelodic Morenia
  • ROS環境をDocker上で構築した
    • ROSのArduino用シリアル通信ライブラリの抽出をビルドなしで行える

動作環境

  • Windows 10 1809
    • Docker Desktop Community 2.0.0.3
    • Arduino IDE 1.8.9

手元に手頃なデスクトップPCがなかったのでOSはWindowsを使うことにし、Docker Desktopを使用してコンテナ内にROS環境を構築することにした。
Gazeboなどシミュレータ(GUI)を使う予定があるならこの方法は使えないことに注意。

ESP32とWindowsはUSBで接続し、Arduinoプログラムのコーディング・ビルド・書き込みはホストOS(Windows)上にインストールしたArduino IDEで行う。

ESP32と通信するROSはDockerコンテナ上に構築する。

上述のとおり、ESP32とWindowsは物理的に接続されているが、ESP32とROSの間の通信はWiFiによる無線通信で行う。

セットアップ

Docker Desktopのインストールは省略する。

rosserial入りDockerイメージの作成

ROSのイメージファイルはros:melodic-robotとして配布されているが、
今回やろうとしているArduiono(ESP32)との通信にはrosserialパッケージを別途インストールする必要がある。
ros:melodic-robotをrunしてからインストールしてもよいが、せっかくなのでDockerfileを作ってrosserialのインストールまでイメージ化しておく。

  1. 適当な空フォルダを用意する
    • 以降ではC:\tmp\ros_envを用意したものとする
  2. 本節下部に示すDockerfileをC:\tmp\ros_env\Dockerfileとして置く
  3. コマンドプロンプトやPowerShell上でdocker build -t local_ros/rosserial .\を実行してイメージを作成する

Dockerfile

FROM ros:melodic-robot

# install packages
RUN apt update && apt install -y \
        ros-melodic-rosserial \
        ros-melodic-rosserial-arduino \
    && \
    apt clean && \
    rm -rf /var/lib/apt/lists/*

Arduino IDEのインストール

(1)本体のインストール

公式ページからダウンロード・インストールする。

2019年5月現在なら「Windows Installer, for Windows XP and up」か「Windows app Requires Win 8.1 or 10」がいいと思う。

(2)ESP32用プラグインのインストール

こちらの記事を参考にした。

ESP32-WROOM-32

上記記事の「ESP32-WROOM-32」-「セットアップ」-「ソフトウェア」-「[Arduino core for the ESP3 のインストール (ライブラリマネージャ使用)]」を参照。

(3)ros_libの抽出とインストール

ArduinoからROSを使うためには専用のC++ライブラリが必要になる(ライブラリとはいっても実体はヘッダファイルなのでビルドは不要)。
今回の構成ではROSをコンテナ上に構築したので、コンテナ上のファイルをホストにコピーする。

通常はgitリポジトリをcloneしビルド・インストールしてパスを通したところでライブラリを抽出するスクリプトを実行するのが正規の手順であるが、いちいちビルドするのは面倒なので自前でコピーする。

  1. cd C:\tmpでカレントディレクトリを移動する
  2. mkdir dockerで作業用フォルダを作成する
  3. docker run -it --rm -v C:\tmp\docker:/root/host local_ros/rosserialを実行してDockerコンテナを起動する
  4. cp -R /opt/ros/melodic/share/rosserial_arduino/src/ros_lib ~/hostを実行してライブラリをコピーする
  5. exitを実行してコンテナを終了する
  6. C:\tmp\Docker\ros_libフォルダを<Arduinoのインストールフォルダ>\libraries\ros_libとして移動する

ビルド

Arduino IDEを開き、以下のプログラムをESP32に書き込む。
プログラムにはWiFiルータのSSIDおよびパスワード、接続先となるIPアドレスが必要になる。
接続先となるIPアドレスについてはホストであるWindowsのIPアドレスでよい。後述のとおりコンテナの起動時にポートフォワーディングオプションをつけるためである。

//
// このソースコードは以下の記事を参考にさせていただきました。
// https://gist.github.com/KobayashiRui/094ac01d9d3cd2445faa2a1ef103646f
//

#include <ros.h>
#include <std_msgs/String.h>
#include <WiFi.h>

const char SSID[] = "<ここにWiFiルータのSSIDを書く>";
const char PASSWORD[] = "<ここにWiFiルータのパスワードを書く>";
IPAddress server(<ここにWindowsIPアドレスを書く>); // e.g.: IPAddress server(192, 168, 1, 3);
const uint16_t serverPort = 11411;

WiFiClient client;

class WiFiHardware {
  public:
  WiFiHardware() {};

  void init() {
    client.connect(server, serverPort);
  }

  int read() {
    return client.read();
  }

  void write(uint8_t* data, int length) {
    for(int i=0; i<length; i++)
      client.write(data[i]);
  }

  unsigned long time() {
     return millis();
  }
};


ros::NodeHandle_<WiFiHardware> nh;

std_msgs::String str_msg;
ros::Publisher chatter("chatter", &str_msg);

char hello[13] = "hello world!";

void setup()
{
  Serial.begin(115200);
  WiFi.begin(SSID,PASSWORD);
    Serial.print("WiFi connecting");

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(100);
  }

  Serial.println(" connected");
  nh.initNode();
  nh.advertise(chatter);
  delay(10);
}

void loop()
{
  str_msg.data = hello;
  chatter.publish( &str_msg );
  nh.spinOnce();
  delay(1000);
}

実行

コンテナを起動してROSマスターを起動し、subscribeする。

前述のとおり接続先としてホストのIPアドレスを指定したため、コンテナの起動においてはポートフォワーディングオプションをつけていることに注意する。

  1. docker run -it --rm -p 11411:11411 -v C:\tmp\docker:/root/host local_ros/rosserialを実行してコンテナを起動する
  2. roscore &を実行してROSマスターを起動する
  3. rosrun rosserial_python serial_node.py tcp &を実行してTCP/IPによるシリアル通信をsubscribeする
  4. ESP32のUSBケーブルを差し直して電源を入れ直す
  5. subscribeに成功したメッセージが表示されたのを確認する
  6. rostopic echo chatterを実行して"hello world!"を受信していることを確認する

以上。

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