DRYな備忘録

Don't Repeat Yourself.

Goのコードから複数の異なるDockerホストに対してコンテナの起動を実装する

2017/06/19 追記

  • エラーハンドリングにバグがあったので修正しました
    • channelに何か流したとき、channelから取り出されないと流し込んだ側をブロックするのを忘れていました
- errored <- err
+ go func() {
+   errored <- err
+ }()

前回までで以下のことをやったので

今回は、Goのソースコードが動いているマシンじゃないマシンに対してDockerコンテナの起動を指示する実装をしてみたいなと思いました。

準備

  • 複数のDockerホストを再現するためにdocker-machineを入手しとく
  • ドライバとしてVirtualBoxをインストールしとく
  • 前回までで使っていたclient.NewEnvClientclient.NewClient実装の違いを見とく
  • docker-machine create --driver virtualbox hoge
    • docker-machine env hoge で得られるDOCKER_HOSTとかDOCKER_CERT_PATHとかあとで使う
  • docker-machine create --driver virtualbox fuga
    • docker-machine env fuga で得られるDOCKER_HOSTとかDOCKER_CERT_PATHとかあとで使う

動くコード

package main

import (
    "bufio"
    "context"
    "fmt"
    "net/http"
    "path/filepath"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/api/types/container"
    "github.com/docker/docker/api/types/mount"
    "github.com/docker/docker/api/types/network"
    "github.com/docker/docker/client"
    "github.com/docker/go-connections/tlsconfig"
)

// WorkerMachine is a host on which any container runs,
// This shoule also have file mout source path.
type WorkerMachine struct {
    Host        string
    CertPath    string
    MountSource string
}

// NewClient provides Docker client for **THIS** host machine.
func (machine WorkerMachine) NewClient() (*client.Client, error) {
    options := tlsconfig.Options{
        CAFile:             filepath.Join(machine.CertPath, "ca.pem"),
        CertFile:           filepath.Join(machine.CertPath, "cert.pem"),
        KeyFile:            filepath.Join(machine.CertPath, "key.pem"),
        InsecureSkipVerify: false,
    }
    tlsc, err := tlsconfig.Client(options)
    if err != nil {
        return nil, err
    }
    httpClient := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: tlsc,
        },
    }
    headers := map[string]string{}
    return client.NewClient(machine.Host, "1.30", httpClient, headers)
}

// This is the definition of docker host machines
// on which any tasks would be executed.
var machines = []WorkerMachine{
    WorkerMachine{
        Host:        "tcp://192.168.99.100:2376",
        CertPath:    "/Users/otiai10/.docker/machine/machines/hoge",
        MountSource: "/Users/otiai10/tmp/hoge",
    },
    WorkerMachine{
        Host:        "tcp://192.168.99.101:2376",
        CertPath:    "/Users/otiai10/.docker/machine/machines/fuga",
        MountSource: "/Users/otiai10/tmp/fuga",
    },
}

// task function defines what to do on that machine.
// Because hijacking stdout of container returns io.Reader
// and also because the first out put would be lost when container starts BEFORE hijacking,
// this function should return channel which can message error,
func task(index int, machine WorkerMachine) (errored chan error) {

    errored = make(chan error)

    c, err := machine.NewClient()
    if err != nil {
        go func() {
            errored <- err
        }()
        return
    }
    defer c.Close()
    ctx := context.Background()

    // Ensure Target Image to exist inside this machine
    r, err := c.ImagePull(ctx, "otiai10/foo", types.ImagePullOptions{})
    if err != nil {
        go func() {
            errored <- err
        }()
        return
    }
    defer r.Close()
    fmt.Printf("[%d][IMAGE PULL][START]", index)
    for scanner := bufio.NewScanner(r); scanner.Scan(); {
        fmt.Printf(".")
    }
    fmt.Printf("\n[%d][IMAGE PULL][FINISH]\n", index)

    // Create container in this machine
    containerconfig := &container.Config{Image: "otiai10/foo"}
    hostconfig := &container.HostConfig{
        Mounts: []mount.Mount{
            mount.Mount{
                Type:   mount.TypeBind,
                Source: machine.MountSource,
                Target: "/var/data",
            },
        },
        AutoRemove: true,
    }
    networkingconfig := &network.NetworkingConfig{}
    body, err := c.ContainerCreate(
        ctx,
        containerconfig, hostconfig, networkingconfig,
        fmt.Sprintf("work-%d", index),
    )
    if err != nil {
        go func() {
            errored <- err
        }()
        return
    }

    // Hijack container's Stdout, and it should be hijacked BEFORE container starts
    hijacked, err := c.ContainerAttach(ctx, body.ID, types.ContainerAttachOptions{
        Stream: true,
        Stdout: true,
    })
    if err != nil {
        go func() {
            errored <- err
        }()
        return
    }
    go func() {
        for scanner := bufio.NewScanner(hijacked.Reader); scanner.Scan(); {
            fmt.Printf("[%d][CONTAINER STDOUT]\t%s\n", index, scanner.Text())
        }
        hijacked.Close()
        fmt.Printf("[%[1]d][COMPLETED] Congrats! `work-%[1]d` is completed without errors!\n", index)
        errored <- nil
    }()

    // OK, now it's time to start the container!
    if err := c.ContainerStart(ctx, body.ID, types.ContainerStartOptions{}); err != nil {
        go func() {
            errored <- err
        }()
        return
    }

    return
}

func main() {
    for i, machine := range machines {
        errored := task(i, machine)
        if err := <-errored; err != nil {
            panic(err)
        }
        close(errored)
    }
}

これを実行すると、

f:id:otiai10:20170607171124p:plain

となり、複数の異なるホストマシンで、異なるContainerのプロセスが、同期的に逐次実行されていることがわかる。

なお、二回目からは、各ホストマシンにimageがすでにあるので

f:id:otiai10:20170607171538p:plain

こういう感じになるし、あえて

% eval $(docker-machine env fuga)
% docker images -a
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
otiai10/foo         latest              92af8256ff46        39 minutes ago      193 MB
% docker rmi otiai10/foo
%

このようにfugaの方だけでotiai10/fooimageを消しとくと、

f:id:otiai10:20170607172406p:plain

fugaでのみdocker pullでまるっとimageを落としてくる必要があり、hogefugaがDockerホストマシンとして全く別物であることが確認できる。

雑感

  • Dockerちょっとずつわかってきた
  • 去年ずっとJSとSwiftだったので、ひさしぶりにGo触ってて超たのしい
  • GoたのしすぎてJSのタスクがぜんぜん進まない…

DRYな備忘録として

Docker

Docker

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ

【GAE/Go】goappで "There are too many files in your application" と怒られる

追記 2018/08/01

gcloud components update 来てたので、した。

追記

gcloud components updateするとgoogle-cloud-sdk以下のファイルが更新されて魔改造が吹っ飛ぶ。かなしみ。

問題

% goapp serve
INFO     2017-06-07 01:58:24,616 devappserver2.py:692] Skipping SDK update check.
INFO     2017-06-07 01:58:24,646 api_server.py:272] Starting API server at: http://localhost:52623
INFO     2017-06-07 01:58:24,648 dispatcher.py:205] Starting module "default" running at: http://localhost:8080
INFO     2017-06-07 01:58:24,650 admin_server.py:116] Starting admin server at: http://localhost:8000
/Users/otiai10/.google-cloud-sdk/platform/google_appengine/google/appengine/tools/devappserver2/mtime_file_watcher.py:157: UserWarning: There are too many files in your application for changes in all of them to be monitored. You may have to restart the development server to see some changes to your files.
  'There are too many files in your application for '

となって、編集検知のオートビルドされなくてちょっと困る。

調査

なんかdevappserverのコード魔改造するしかなさそう。今んとこ。

解決

appengine/tools/devappserver2/mtime_file_watcher.pyを以下のように編集。まずはわかりやすいように。

diff --git a/watcher_common.py b/watcher_common.py
index fe47078..6541de6 100755
--- a/watcher_common.py
+++ b/watcher_common.py
@@ -47,6 +47,11 @@ def ignore_file(filename, skip_files_re=None):
   Returns:
     Boolean value, True if the filename can be ignored.
   """
+  if not filename.startswith(os.getcwd()):
+    return True
+  if filename.find('/node_modules/') is not -1:
+    return True
+
   if skip_files_re and skip_files_re.match(filename):
     return True
   filename = os.path.basename(filename)
  1. カレントディレクトリ以下のファイル以外は監視対象から除外
  2. node_modulesは監視対象から除外

適宜

適宜、たとえば

diff --git a/watcher_common.py b/watcher_common.py
index fe47078..ad1f641 100755
--- a/watcher_common.py
+++ b/watcher_common.py
@@ -47,6 +47,11 @@ def ignore_file(filename, skip_files_re=None):
   Returns:
     Boolean value, True if the filename can be ignored.
   """
+
+  # Watch ONLY current directory if env "GOAPP_WATCH_CWD" is found
+  if os.getenv('GOAPP_WATCH_CWD') and not filename.startswith(os.getcwd()):
+    return True
+
   if skip_files_re and skip_files_re.match(filename):
     return True
   filename = os.path.basename(filename)
@@ -67,7 +72,11 @@ def _remove_pred(lst, pred):

 def ignore_dir(dirpath, dirname, skip_files_re):
   """Report whether a directory should not be watched."""
+
+  # List of directory names to be ignored; e.g. "node_modules", "vendor" or so
+  _IGNORED_DIRS = ('node_modules')
   return (dirname.startswith(_IGNORED_PREFIX) or
+         (dirname in _IGNORED_DIRS) or
           skip_files_re and skip_files_re.match(os.path.join(dirpath, dirname)))

のようにしたらオシャレなのではないか。魔改造には変わりないけど。

DRYな備忘録として

はじめてのGoogle App Engine Go言語編 (I・O BOOKS)

はじめてのGoogle App Engine Go言語編 (I・O BOOKS)

Go言語プログラミング入門on Google App Engine

Go言語プログラミング入門on Google App Engine

プログラミング Google App Engine

プログラミング Google App Engine

CSS書きたくなさすぎ問題2017

css書きたくない。できればjsも書きたくない。js必要なの嫌。軽くやりたい。という個人の日記です。

参考

Gridだけやりたいやつは除外した。

  • Bootstrap
  • Materialize
  • mui
  • (追記)UIkit
  • (追記)Semantic UI
  • Pure
  • Bulma
  • Skelton
  • Spectre.css
  • Kube
  • Vuetify
  • Fictoan
  • avalanche
  • Beuter
  • Vanilla
  • Milligram
  • InvisCSS
  • Look
  • mini
  • Cutestrap
  • Buefy
  • siimple
  • Mobi
  • Modulr
  • Grd
  • Frow
  • Picnic CSS
  • Basscss
  • Blaze CSS
  • Furtive
  • LOTUS
  • Leaf CSS

後半疲れたっていうのもあり、僕的には、Skelton、Spectre、Fictoan、Milligram、Look、あたりが今後使ってみたいです。

Bootstrap

Bootstrap · The world's most popular mobile-first and responsive front-end framework.

大御所だし、最初に言っといてやるか感

Materialize

Documentation - Materialize

f:id:otiai10:20170530145913p:plain

material-uiのcssだけ版。個人事業で使ってるけど、部分的にjs要るし、ちょっとだるみ出てきた。これ使うならMaterial-UIでええやろ感ある。なお、Material-UIは個人趣味開発で使ってる。

mui

MUI - Material Design CSS Framework

f:id:otiai10:20170530150136p:plain

Materializeと同じ雰囲気を感じる。jsあるやん。

(追記)UIkit

Thanks to id:k-holy, id:kvx

UIkit

jQuery is required as well.

アッ、ハイ。

(追記)Semantic UI

Thanks to id:oukayuka, id:wazly

Semantic UI

Just link to these files in your HTML along with the latest jQuery.

アッ、ハイ。

Reactでやりたいんだったら Semantic UI React があるけど、僕が常日頃だるいなって思ってるのは、デザインとフロントのアーキテクチャがベタベタでいいのか?っていうアレ。変じゃね?なんでデザイン策定でReact使うかjQuery使うかみたいな二択せなアカンねんと。両方つかうとか論外。

Pure

Pure

f:id:otiai10:20170530150038p:plain

cssオンリーつったらいの一番に出てくるやつ

Bulma

Bulma: a modern CSS framework based on Flexbox

f:id:otiai10:20170530150843p:plain

わりといい感じ。demadoに使ってる。

Skelton

Skeleton: Responsive CSS Boilerplate

f:id:otiai10:20170530145504p:plain

こういうのでいいんだよ、こういうので。

Spectre.css

f:id:otiai10:20170530151001p:plain

よさそう。

Kube

f:id:otiai10:20170530151207p:plain

パンくずとかもあっておもしろそうだけど、JSけっこう書かされる気がする。

Vuetify

vuetifyjs.com

Vueコンポーネントベース。却下。

Fictoan

FICTOAN • Intuitive, minimalist responsive HTML+CSS boilerplate

f:id:otiai10:20170530151936p:plain

Overview読んで、あ〜これ好き、ってなった。今度使いたい

avalanche

avalanche | A package based CSS framework.

プロジェクト構成まで口出してこないでほしい

Beuter

Beauter | A simple framework for beautiful sites

f:id:otiai10:20170530152449p:plain

完全に好みで申し訳ないんだけど、トップページのUIがあんまり好きくなかったので印象悪い

Vanilla

Documentation

f:id:otiai10:20170530152631p:plain

menu itemがらみのクリック可領域がちょっと直感的ではない。そのへんもカスタマイズしろ、ってことなのかな

Milligram

Milligram - A minimalist CSS framework.

f:id:otiai10:20170530153019p:plain

一瞬Vaporかな?って思ったけどちがう。圧巻の2kb。良いかもしれない。

InvisCSS

Invis CSS

なんかそういうことじゃねえんだよなー、っていう。僕のペインとスタートラインが違う。

Look

Look | Minimalistic CSS framework

f:id:otiai10:20170530153833p:plain

FieldsetsとかSegmentsとかおもしろそう。最後のCubeワロタ。

mini

mini.css - Minimal, responsive, style-agnostic CSS framework

f:id:otiai10:20170530154652p:plain

全体的にカクカクしてた

Cutestrap

Cutestrap

f:id:otiai10:20170530154909p:plain

さすがに機能薄すぎるんじゃねえかなと思った。

Buefy

Buefy: lightweight UI components for Vue.js based on Bulma

Vueのコンポーネントかー、残念。

siimple

siimple - Minimal CSS framework for flat and clean websites

f:id:otiai10:20170530155216p:plain

名前がややこしい。Almost Flat UIに似た印象を受けた。

Mobi

Mobi.css

Mobile Firstらしいけど、たぶん使わない。見た目が無骨すぎて。

Modulr

Modulr.css Frontend Framework

f:id:otiai10:20170530155717p:plain

色に対する並々ならぬ関心が感じられるが、これ↑残念すぎるだろ。

Grd

Grd - A CSS grid framework using Flexbox

もう頭使いたくないんです。ゆるして。

Frow

Frow CSS

f:id:otiai10:20170530160100p:plain

ほぼほぼGridとFormだけって感じ。でもFormはスタイリッシュでいい感じ。

Picnic CSS

Picnic CSS

f:id:otiai10:20170530160404p:plain

なかなかよい。「ここはすまんやけどjs書いてくれ」っていう態度も好印象です。書かねえけどな。

Basscss

Basscss

f:id:otiai10:20170530160556p:plain

Form置いてけぼりでいいんならアリなんじゃないですかね。

Blaze CSS

Blaze CSS - Open Source Modular CSS Toolkit

f:id:otiai10:20170530161108p:plain

なんかどっかで見たことあるんだよなあこの雰囲気。

Furtive

Furtive CSS

f:id:otiai10:20170530161232p:plain

Skeltonをちょっとカラフルにした印象がある。よさそう。

LOTUS

f:id:otiai10:20170530161407p:plain

フォームはシュッとしてるけどぜんたいてきに柔らかい印象。

Leaf CSS

Leaf BETA 1.0 - CSS Framework

f:id:otiai10:20170530161656p:plain

Material Designの軽量実装っぽい。いい感じなのだけれど、あまりメンテされなさそう。

雑感

  • 調べればキリがないわコレ
  • なんか適当な軸でマッピングにしてビジュアライズしてくれ誰か頼む
  • プログラマCSS考えさせるデザイナと仕事すんのつらい。デザイナからCSSフレームワーク納品されたら泣いて喜ぶ。せめてガイドラインぐらい作ってくれ。「え?出来ないんですか?ww」じゃねえよ、おめえのsketchファイルmarginあちこちバラバラだから!

GoのコードからDockerコンテナへのディスクボリュームのマウントを実装する

前々回、前回

で、GoのコードからDockerイメージのpullとDockerコンテナのrunを実装できたので、実践的なアプリケーションをつくっていきたいのだけれど、今回はdocker runにおける--volumeの指定がしたい。

実行するDockerイメージのサンプル

Dockerfile

FROM centos

ADD main.sh /bin/

ENTRYPOINT main.sh

main.sh

#!/bin/sh

dest="copy_`date`.txt"
cat /var/data/foo.txt > /var/data/${dest}

docker build . -t foo としてfooというtagのimageをつくる。

実行するGoのコード

   cc := &container.Config{
        Image: "foo",
        // Volumes: map[string]struct{}{"/var/data": struct{}{}},
        // これ要らんっぽいな... 🤔
    }
    hc := &container.HostConfig{
        // これが味噌
        Mounts: []mount.Mount{
            mount.Mount{
                Type:   mount.TypeBind,
                Source: "/Users/otiai10/tmp/hoge",
                Target: "/var/data",
            },
        },
        AutoRemove: true,
    }
    nc := &network.NetworkingConfig{}

    // あとは前回、前々回とおなじです。
    body, err := c.ContainerCreate(ctx, cc, hc, nc, "bar")
    if err != nil {
        panic(fmt.Errorf("ContainerCreate: %v", err))
    }
    fmt.Printf("Created Container:\n%+v\n", body)

    if err := c.ContainerStart(ctx, body.ID, types.ContainerStartOptions{}); err != nil {
        panic(fmt.Errorf("ContainerStart: %v", err))
    }

確認

% ls -l ~/tmp/hoge
total 8
-rw-r--r--  1 otiai10  staff  16  5 29 16:48 foo.txt
% go run main.go
Created Container:
{ID:e2b6e7aa7251f13d8e823dc4a4dddb327b3863e9632211698126b46d5975e724 Warnings:[]}
List of containers
 - e2b6e7aa7251f13d8e823dc4a4dddb327b3863e9632211698126b46d5975e724 (foo)
% ls -l ~/tmp/hoge
total 16
-rw-r--r--  1 otiai10  staff  16  5 30 11:59 copy_Tue May 30 02:59:06 UTC 2017.txt
-rw-r--r--  1 otiai10  staff  16  5 29 16:48 foo.txt
%

無事、ホスト(厳密にはGoのコードを実行する環境ではなく、このclient.Clientが向いてるdockerホストマシン)の/Uers/otiai10/tmp/hogeが、コンテナから見たときの/var/dataにマウントされていることが確認された。

参考と道のり

f:id:otiai10:20170530120709p:plain

雑感

  • ドキュメント散在してるなー、って思った
  • 急がず焦らず、じっくり読まないといけない

DRYな備忘録として

Docker

Docker

Dockerエキスパート養成読本[活用の基礎と実践ノウハウ満載!] (Software Design plus)

Dockerエキスパート養成読本[活用の基礎と実践ノウハウ満載!] (Software Design plus)

GoのコードからDockerイメージのpullを実装する(bufio.Scannerかわいい)

前回エントリ↓でコードからのイメージのpullが動かなくてあっれおかしーなーとなって悔しかったのでリベンジです。

otiai10.hatenablog.com

tl;dr

  • client.ImagePullの返り値はio.ReadCloser型とerror
  • このio.ReadCloserが、イメージのpullのprogressなどを表すHTTPのストリーム
  • このストリームへの書き込みが終わる(つまりdocker pullが完了する)まで待つ必要があった

以下読まなくてよいです

動かないコード

func main() {

    c, err := client.NewEnvClient()
    if err != nil {
        panic(fmt.Errorf("NewEnvClient: %v", err))
    }
    ctx := context.Background()

    // "debian"(:latestは省略可)のpullを試みる
    rp, err := c.ImagePull(ctx, "debian", types.ImagePullOptions{})
    if err != nil {
        panic(fmt.Errorf("ImageSave: %v", err))
    }
    defer rp.Close()
    // エラーさえ無ければイメージのpullが成功していると誤解していた

    cc := &container.Config{Image: "debian"}
    hc := &container.HostConfig{AutoRemove: true}
    nc := &network.NetworkingConfig{}
    body, err := c.ContainerCreate(ctx, cc, hc, nc, "bar")
    if err != nil {
        panic(fmt.Errorf("ContainerCreate: %v", err))
        // 結局ここで"debianなんてイメージは無い!"と叱られる
    }
    fmt.Printf("Created Container:\n%+v\n", body)

}

動くコード

前後省略します

func main() {

    /* 省略(クライアントの初期化とかする) */

    // "debian"(:latestは省略可)のpullを試みる
    rp, err := c.ImagePull(ctx, "debian", types.ImagePullOptions{})
    if err != nil {
        panic(fmt.Errorf("ImageSave: %v", err))
    }
    defer rp.Close()

    // {{{ こっからが味噌!!

    // こういう構造のバッファが書き込まれる
    payload := struct {
        ID             string `json:"id"`
        Status         string `json:"status"`
        Progress       string `json:"progress"`
        ProgressDetail struct {
            Current uint16 `json:"current"`
            Total   uint16 `json:"total"`
        } `json:"progressDetail"`
    }{}

    // bufio.Scannerマジでかわいい
    scanner := bufio.NewScanner(rp)
    for scanner.Scan() {
        json.Unmarshal(scanner.Bytes(), &payload)
        fmt.Printf("\t%+v\n", payload)
    }

    // }}} ここまでが味噌!!

    /* 省略(得られたイメージを指定してコンテナつくったりする) */

}

上記では、bufio.Scannerを使ってHTTPのEOFまで待っている。ついでに、毎バッファ書き込みされるレスポンスを取り出して上げている。
↓こういうきれいな感じのペイロードが送られてくる。

f:id:otiai10:20170526152637p:plain

Scanner使わないんだったらioutil.ReadAllかなー他にストリームが終わるの同期的に待つのどうすっかなーと思ってググりました。

今回のように毎ペイロードが独立して意味のあるものであれば、Scanner使うのが良い気がするものの、もっと良い(単にReaderのEOFを待つための)方法があれば教えてくださいm(_ _)m

雑感

  • Goのコードからgithub.com/docker/docker/clientを使って (1) イメージのpull (2) コンテナ起動 が実現されたので、これでほぼ心置きなく俺々Dockerクライアントを自作できますね!やったね!
  • 腰椎椎間板ヘルニアになっちゃった。腰痛ぇ。

DRYな備忘録として

Docker

Docker

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

GoのコードからDockerコンテナの起動を実装する

やったこと

  • docker clientをGoのコードからimportする
  • container作成
  • container起動
  • container停止(& 自動削除)

苦労したこと

  • けっこうdocker(現moby)のコードの移り変わりが激しくて、vendorを固定するのが苦労した

できなかったこと

main.go

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/api/types/container"
    "github.com/docker/docker/api/types/network"
    "github.com/docker/docker/client"
    "github.com/docker/go-connections/nat"
)

func main() {

    c, err := client.NewEnvClient()
    if err != nil {
        panic(fmt.Errorf("NewEnvClient: %v", err))
    }

    ctx := context.Background()

    /* 後述する「うごかなかったもの」がここに来ます */

    // 1) コンテナをつくる
    cc := &container.Config{
        Image:        "nginx",
        ExposedPorts: nat.PortSet{nat.Port("80"): struct{}{}},
        // Entrypoint:   strslice.StrSlice{"nginx", "-g", "daemon off;"},
    }
    hc := &container.HostConfig{
        // `--port 8080:80`
        PortBindings: nat.PortMap{
            nat.Port("80"): []nat.PortBinding{nat.PortBinding{HostPort: "8080"}},
        },
        // `--rm`
        AutoRemove: true,
    }
    // docker network らへん
    nc := &network.NetworkingConfig{}

    body, err := c.ContainerCreate(ctx, cc, hc, nc, "foo")
    if err != nil {
        panic(fmt.Errorf("ContainerCreate: %v", err))
    }
    fmt.Printf("Created Container:\n%+v\n", body)

    // 2) コンテナのスタート
    if err := c.ContainerStart(ctx, body.ID, types.ContainerStartOptions{}); err != nil {
        panic(fmt.Errorf("ContainerStart: %v", err))
    }

    // あること確認
    check(ctx, c)

    // 3) 20秒だけ延命
    time.Sleep(20 * time.Second)

    // 4) コンテナの停止
    //    --rm を投げてるのでコンテナ自体も削除される
    timeout := 5 * time.Second
    if err := c.ContainerStop(ctx, body.ID, &timeout); err != nil {
        panic(fmt.Errorf("ContainerStop: %v", err))
    }

    // ないこと確認
    check(ctx, c)
}

// コンテナのリストを取得する確認用の関数
func check(ctx context.Context, c *client.Client) {

    containers, err := c.ContainerList(ctx, types.ContainerListOptions{All: true})
    if err != nil {
        panic(err)
    }

    fmt.Println("List of containers")
    for _, container := range containers {
        fmt.Printf(" - %s (%s)\n", container.ID, container.Image)
    }
}

これで、 go run main.go とすると、20秒だけホストの8080にnginxが現れるということになります。

f:id:otiai10:20170524163022p:plain

f:id:otiai10:20170524162509p:plain

f:id:otiai10:20170524163152p:plain

よっしゃ。

うごかなかったもの

   rp, err := c.ImagePull(ctx, "nginx:latest", types.ImagePullOptions{})
    if err != nil {
        panic(fmt.Errorf("ImagePull: %v", err))
    }
    defer rp.Close()
    // エラーは無いんだけど、ContainerCreateで参照できないんだよなあ

    // じゃあImageSaveかなと思ったけど引数がImageIDsなので、とりあえずハードコードで投げる
    rs, err := c.ImageSave(ctx, []string{"bf2b4c2d7bf53b4d0d28fa6af60e51c418317d2ada40ed6e5d5c290248d2a469"})
    if err != nil {
        panic(fmt.Errorf("ImageSave: %v", err))
    }
    defer rs.Close()
    // まあ無いと言われる

    // 確認用
    images, err := c.ImageList(ctx, types.ImageListOptions{})
    if err != nil {
        panic(fmt.Errorf("ImageList: %v", err))
    }
    for _, img := range images {
        fmt.Printf("%+v\n", img)
    }

    // ImagePullで得たio.ReadCloserに色々入ってんのかな?
    // やたらでかそうだし、もしかしてイメージそのもの?

この部分が動かなかったので、あらかじめホストマシンで docker pull nginx を実行しないと動かないコードになってしまった。くやしい。次はこれを解決したいです。

DRYな備忘録として

追記

できました otiai10.hatenablog.com

Docker

Docker

不要になったDockerコンテナの一括削除

% docker ps -a -q -f "status=exited" | xargs docker rm
  • -a --all runningではないコンテナも含めすべて表示
  • -q --quiet container idのみ表示
  • -f --filter 色々でフィルタリングできる
  • を、xargsdocker rmの引数に投げた

DRYな備忘録として