ドッカー + ゴラン = <3

これは、Goコードを操作するときにDockerがどのように役立つかを示すヒントとコツの短いコレクションです。 たとえば、異なるバージョンの Go ツールチェーンで Go コードをコンパイルする方法、別のプラットフォームにクロスコンパイルする方法 (そして結果をテストする方法)、非常に小さなコンテナー イメージを生成する方法などを紹介します。

次の記事では、システムに Docker がインストールされていることを前提としています。 最新バージョンである必要はありません(ここでは特別な機能は使用しません)。

なしで行く 行く

...そしてそれによって、私たちは「インストールせずに行く 行く".

Goコードを書く場合、またはGo言語に少しでも興味がある場合は、確かにGoコンパイラとツールチェーンがインストールされているので、「ポイントは何ですか?」と疑問に思うかもしれません。しかし、GoをインストールせずにGoをコンパイルしたいシナリオがいくつかあります。

  • マシンにはまだこの古いGo 1.2があり(アップグレードできない、またはアップグレードしない)、新しいバージョンのツールチェーンを必要とするこのコードベースで作業する必要があります。
  • Go 1.5のクロスコンパイル機能を試してみたい(たとえば、LinuxシステムからOS Xバイナリを作成できるようにするため)。
  • 複数のバージョンのGoを並べて使用したいが、システムを完全に散らかしたくはない。
  • プロジェクトとそのすべての依存関係がクリーンなシステムで正常にダウンロード、ビルド、および実行されることを100%確実にする必要があります。

これのいずれかがあなたに関連している場合は、Dockerに救助を呼びましょう!

コンテナ内のプログラムのコンパイル

Goをインストールしたら、ライブラリをダウンロード、ビルド、およびインストールできます go get -v github.com/user/repo 。 (フラグは -v 冗長性のためにここにありますが、ツールチェーンを迅速かつサイレントにしたい場合は削除できます!

go get github.com/user/repo/... また、そのリポジトリ内のすべてのもの(ライブラリやバイナリを含む)をダウンロード、ビルド、インストールすることもできます(はい、それは3つのドットです)。

コンテナでそれを行うことができます!

これを試してください:

ドッカーはゴランを走らせて-v github.com/golang/example/hello/ を取得します...

これにより、golangイメージがプルされ(すでにお持ちでない限り、すぐに起動します)、そのイメージに基づいてコンテナが作成されます。 そのコンテナで、goは小さな「hello world」の例をダウンロードし、ビルドしてインストールします。 しかし、それはコンテナにインストールされます...では、そのプログラムを今どのように実行するのでしょうか。

コンテナでのプログラムの実行

1つの解決策は、ビルドしたばかりのコンテナを コミット する、つまり新しいイメージに「フリーズ」することです。

docker commit $(docker ps -lq) awesomeness

注: docker ps -lq 最後に実行されたコンテナの ID (および ID のみ) を出力します。 マシン上で唯一の uesr であり、前のコマンド以降に別のコンテナーを作成していない場合、そのコンテナーは "hello world" の例をビルドしたばかりのコンテナーである必要があります。

これで、ビルドしたイメージに基づいてコンテナーでプログラムを実行できます。

ドッカー実行の素晴らしさこんにちは

出力は Hello, Go examples!.

ボーナスポイント

を使用して docker commitイメージを作成する場合は、フラグ --change を使用して任意の Dockerfile コマンドを指定できます。 たとえば、または ENTRYPOINT コマンドを使用して docker run awesomeness 、 CMD helloを自動的に実行できます。

使い捨てコンテナで走る

このGoプログラムを実行するためだけに追加のイメージを作成したくない場合はどうなりますか?

私たちはあなたをカバーしてもらいました:

docker run --rm golang sh -c \
「github.com/golang/example/hello/ を取りに行く...&& exec hello"

ちょっと待ってください、それらすべてのベルとホイッスルは何ですか?

  • --rm コンテナーが終了したら自動的にコマンドを発行 docker rm するように Docker CLI に指示します。 そうすれば、私たちは自分の後ろに何も残しません。
  • ビルドステップ()と実行ステップ(go getexec hello)をシェル論理演算子 &&を使用して連鎖させます。あなたがシェル愛好家でない場合、「 && と」を意味します。 最初の部分が実行され、その部分が成功した場合 (そして成功した場合のみ)、2 番目の部分 go get...(exec hello). なぜこれがそのようなのか疑問に思うなら、それは怠惰 and な評価者のように機能し、左側 trueが.
  • コマンドをに sh -c渡すのは、単に実行する docker run golang "go get ... && hello"と、Dockerはという名前の go SPACE get SPACE etcプログラムを実行しようとするためです。 代わりに、シェルを起動し、コマンドシーケンスを実行するようにシェルに指示します。
  • の代わりに hello 使用します exec hello:これにより、現在のプロセス(開始したシェル)がプログラムに置き換えられ hello ます。hello これにより、シェルをPID 1および hello 子プロセスとして持つのではなく、コンテナ内のPID 1になります。これはこの小さな例ではまったく役に立ちませんが、より有用なプログラムを実行すると、外部信号がコンテナのPID 1に配信されるため、外部信号を適切に受信できるようになります。 どんな信号なのか、疑問に思われるかもしれません。 良い例は docker stopSIGTERM コンテナ内のPID 1に送信することです。

別のバージョンの Go を使用する

イメージを使用すると golang 、Docker は (ご想像のとおり) Docker Hub で使用可能な最新バージョンにマップされるイメージ golang:latest, を展開します。

特定のバージョンのGoを使用したい場合、それは非常に簡単です:イメージ名の後にそのバージョンを タグ として指定します。

たとえば、Go 1.5を使用するには、上記の例を変更して次のように置き換え golang golang:1.5ます。

docker run --rm golang:1.5 sh -c \
 「github.com/golang/example/hello/ を取りに行く...&& exec hello"

利用可能なすべてのバージョン (およびバリアント) は、Docker Hub の Golang イメージ ページで 確認できます。

システムへのインストール

では、コンパイルされたプログラムをコンテナではなくシステムで実行する場合はどうでしょうか。

コンパイルされたバイナリをコンテナからコピーできます。 ただし、これはコンテナアーキテクチャがホストアーキテクチャと一致する場合にのみ機能することに注意してください。言い換えれば、実行した場合 ドッカー オン リナックス。 (私はWindowsコンテナを実行しているかもしれない人々を除外しています!

コンテナーからバイナリを取得する最も簡単な方法は、ディレクトリを $GOPATH/bin ローカル ディレクトリにマップすることです。 コンテナ golang 内では/go. 、 $GOPATH したがって、次のことができます。

docker run -v /tmp/bin:/go/bin \
 ゴランは github.com/golang/example/hello/ を取りに行きます...
 /tmp/bin/hello

Linuxを使用している場合は、 Hello, Go examples! メッセージ。 ただし、たとえばMacを使用している場合は、おそらく次のように表示されます。

-バッシュ:
/tmp/test/hello: バイナリ ファイルを実行できません

私たちはそれについて何ができますか?

クロスコンパイル

Go 1.5には、 すぐに使用できる優れたクロスコンパイル機能が搭載されているため、コンテナのオペレーティングシステムやアーキテクチャがシステムのものと一致しなくても、まったく問題ありません。

クロスコンパイルを有効にするには、 GOOS および/または GOARCHを設定する必要があります。

たとえば、64ビットMacを使用していると仮定します。

docker run -e GOOS=darwin -e GOARCH=amd64 -v /tmp/crosstest:/go/bin \ゴランは github.com/golang/example/hello/ を取りに行きます...

クロスコンパイルの出力は、 ではなく $GOPATH/bin、 つまり、プログラムを実行するには、 $GOPATH/bin/$GOOS_$GOARCH. /tmp/crosstest/darwin_amd64/hello.

に直接インストールします $PATH

Linuxを使用している場合は、システム bin ディレクトリに直接インストールすることもできます。

docker run -v /usr/local/bin:/go/bin \
 ゴランは github.com/golang/example/hello/ を取得します...

ただし、Macでは、ボリュームとして使用 /usr しようとしても、Macのファイルシステムはコンテナにマウントされません。 Moby VM (ツールバーの Docker クジラ アイコンの後ろに隠れている小さな Linux VM) のディレクトリがマウント /usr されます。

ただし、ホームディレクトリ内の何かを使用して /tmp 、そこからコピーすることはできます。

無駄のないイメージの構築

この手法で作成したGoバイナリは静的 にリンクされています。 これは、すべての依存関係を含む、実行に必要なすべてのコードを埋め込むことを意味します。 これは、いくつかの基本的なライブラリ(「libc」など)を含まず、実行時に解決されるシステム全体のコピーを使用する動的にリンクされた プログラムとは対照的です。

これは、Goコンパイルされたプログラムを他に 何もせずにコンテナにドロップできることを意味し、機能するはずです。

これを試してみよう!

掻く 画像

Dockerエコシステムには特別なイメージがあります。 scratch. これは空の画像です。 定義上、空であるため、作成またはダウンロードする必要はありません。

新しい Go リーン イメージ用に新しい空のディレクトリを作成しましょう。

この新しいディレクトリに、次の Dockerfile を作成します。

ゼロから
 コピー ./こんにちは /こんにちは
 エントリポイント ["/hello"]

つまり、* 最初から始める (空のイメージ)、* イメージをルートにファイルを追加する hello 、* このコンテナの起動時に実行する hello デフォルトのプログラムとしてこのプログラムを定義する。

次に、次のようにバイナリを生成します hello 。

docker run -v $(pwd):/go/bin --rm \
 ゴランは github.com/golang/example/hello/ を取りに行きます...

注:正確には、ホストシステムではなく Dockerコンテナで 実行されるバイナリが必要なため、ここで設定 GOOS GOARCH する必要はありません。したがって、これらの変数はそのままにしておきます。

次に、イメージをビルドできます。

ドッカービルド-tこんにちは。

そしてそれをテストします:

ドッカーはこんにちはを実行します

(これにより、Hello, Go の例が表示されます。

最後になりましたが、画像のサイズを確認してください。

ドッカーイメージこんにちは

すべてを正しく行った場合、この画像は約2MBになるはずです。 悪くありません!

GitHub にプッシュせずに何かを構築する

もちろん、コンパイルするたびにGitHubにプッシュする必要がある場合は、多くの時間を無駄にします。

コードの一部を操作してコンテナー内でビルドする場合は、コンテナー内に golang ローカル ディレクトリ /go をマウントして $GOPATH 、呼び出し間で永続化されるようにします。docker run -v $HOME/go:/go golang ....

ただし、ローカルディレクトリを特定のパスにマウントして、一部のパッケージ(ローカルで編集したもの)を「オーバーライド」することもできます。 完全な例を次に示します。

#Macで実行していない場合は、次の2つの環境変数を適応させます
 エクスポート GOOS=ダーウィン GOARCH=amd64
 mkdirゴーアンドドッカーは愛です
 cdゴーアンドドッカーは愛です
 git クローン git://github.com/golang/example 猫の例/こんにちは/こんにちは.go
 sed -i .bak s/olleH/eyB/ example/hello/hello.go
 ドッカー実行 --rm \
 -v $(pwd)/example:/go/src/github.com/golang/example \
 -v $ (pwd) :/go/bin/${GOOS}_$ { {GOARCH} } \ -e GOOS -e GOARCH \
 ゴランは github.com/golang/example/hello/ を取りに行きます...
 。/こんにちは#「さようなら、例に行く!」を表示する必要があります。

 

の特殊なケース  パッケージと CGo

実際の囲碁コードに飛び込む前に、静的バイナリについて少し嘘をついたことを告白する必要があります。 CGo を使用している場合、またはパッケージを使用している場合 net 、Go リンカーは動的バイナリを生成します。 パッケージの場合 net (そこにある 多くの 便利なGoプログラムが実際に使用しています!)、主な原因はDNSリゾルバです。 そこにあるほとんどのシステムには、技術的には動的ライブラリであるプラグインに依存する、派手なモジュール式の名前解決システム( ネームサービススイッチなど)があります。 デフォルトでは、Goはそれを使用しようとします。そしてそうするために、それは動的ライブラリを生成します。

どうすればそれを回避できますか?

別のディストリビューションのlibcを再利用する

1つの解決策は、これらのGoプログラムが機能するために必要な必須ライブラリ を備えた 基本イメージを使用することです。 GNU libcに基づくほとんどすべての「通常の」Linuxディストリビューションがこのトリックを行います。 したがって、 の代わりに FROM scratch、 または FROM fedora などを使用します FROM debian。結果の画像ははるかに大きくなります。ただし、少なくとも、大きなビットはシステム上の他のイメージと共有されます。

注意: その場合、アルパインはGNU libcの代わりにmuslライブラリを使用しているため、アルパ インを使用することはできません 。

自分の libc を持ち込む

別の解決策は、必要なファイルを外科的に抽出し、それらをコンテナに配置することです。 COPY. 結果のコンテナは小さくなります。 しかし、この抽出プロセスは、作者に本当に汚い仕事の不安な印象を残し、彼らはむしろ詳細に立ち入りたくないのです。

自分の目で確かめたい場合は、前述したネームサービススイッチプラグインを見回 ldd してください。

静的バイナリの生成 ネットゴ

また、システムのlibc を使用しないように Goに指示し、ネイティブDNSリゾルバに付属しているGoの netgo ライブラリに置き換えることもできます。

使用するには、オプションに追加する -tags netgo -installsuffix netgo go get だけです。

  • -tags netgo ツールチェーンに NetGo を使用するように指示します。
  • -installsuffix netgo 結果のライブラリ(存在する場合)が別のデフォルト以外のディレクトリに配置されていることを確認します。 これにより、複数の go get (または go build)呼び出しを行う場合に、netgoを使用してビルドされたコードとなしでビルドされたコード間の競合を回避できます。 これまでに示したようにコンテナをビルドする場合、このコンテナには他のGoコードがコンパイルされないため、これは厳密には必要ありません。しかし、それに慣れるか、少なくともこのフラグが存在することを知っておくことをお勧めします。

SSL証明書の特殊なケース

コードでSSL証明書を検証する必要がある場合に心配しなければならないことがもう1つあります。たとえば、HTTPS 経由で外部 API に接続する場合などです。 その場合、Goはそれらをバイナリにバンドルしないため、ルート証明書もコンテナに入れる必要があります。

SSL 証明書のインストール

3つ 繰り返しになりますが、利用可能なオプションは複数ありますが、最も簡単な方法は、既存のディストリビューションのパッケージを使用することです。

アルパインはとても小さいので、ここでは良い候補です。 以下は Dockerfile 、小さいがルート証明書の最新のバンドルを持つ基本イメージを提供します。

高山から:3.4APKを実行する追加-キャッシュCA証明書apache2-ユーティリティ

 

試してみて下さい;結果の画像はわずか6MBです!

注: このオプションは --no-cache 、ディスクに保存せずに、Alpine のディストリビューションミラーから利用可能なパッケージのリストを取得するように (Alpine パッケージマネージャーに) 指示 apkします。 Dockerfilesが次のような apt-get update && apt-get install ... && rm -rf /var/cache/apt/*ことをしているのを見たことがあるかもしれません;これは同等の何かを達成します(つまり、 パッケージキャッシュを最終イメージに残さない)を単一のフラグで指定します。

追加のボーナスとして 、アプリケーションをAlpineイメージに基づくコンテナに入れると、たくさんの本当に便利なツールにアクセスできます:必要に応じて、シェルをコンテナにドロップして、実行中にいじくり回すことができます!

まとめ

Dockerがクリーンで分離された環境でGoコードをコンパイルするのにどのように役立つかを見てきました。Goツールチェーンの異なるバージョンを使用する方法。異なるオペレーティングシステムとプラットフォーム間でクロスコンパイルする方法。

また、GoがDocker用の小さくて無駄のないコンテナイメージを構築するのにどのように役立つかを確認し、静的ライブラリとネットワークの依存関係に関連するいくつかの微妙な点について説明しました(しゃれは意図されていません)。

GoがDockerのプロジェクトに本当に適しているという事実を超えて、GoとDockerがお互いからどのように利益を得て、本当にうまく連携できるかをお見せいただければ幸いです。

確認

これは当初、GopherCon 2016のハックデーで発表されました。

この資料を校正し、より良いものにするためのアイデアや提案をしてくれたすべての人々に感謝します。以下を含みますが、これらに限定されません。

すべての間違いやタイプミスは私自身のものです。すべての良いものは彼らのものです! ☺