Dockerイメージを構築するためのLinux from scratchディストリビューション-dappdepsでの経験


ベースイメージに基づいてDockerのイメージをアセンブルするには、通常、そのベースイメージの環境でコマンドを呼び出します。 たとえば、基本イメージにあるapt-getコマンドを呼び出して、新しいパッケージをインストールします。

多くの場合、特定のユーティリティセットをベースシステムに再インストールする必要があります。これにより、最終イメージに必要ないくつかのファイルがインストールまたはアセンブルされます。 たとえば、Goアプリケーションをビルドするには、Goコンパイラをインストールし、すべてのアプリケーションソースコードをベースイメージに配置して、必要なプログラムをコンパイルする必要があります。 ただし、最終イメージでは、このプログラムをコンパイルするために使用されたユーティリティのセット全体がなく、コンパイルされたプログラムのみが必要です。

問題はよく知られています。それを解決する方法の1つは、補助イメージを作成し、補助イメージから結果のファイルにファイルを転送することです。 これを行うために、 Dockerマルチステージビルドまたはアーティファクトイメージがdappに登場しました2019年8月13日更新: dappプロジェクトの名前がwerfに変更され、コードがGoに書き換えられ、ドキュメントが大幅に改善されました)。 また、このアプローチは、ソースコードのコンパイル結果を最終イメージに転送するなどの問題を理想的に解決します。 しかし、彼はすべての可能な問題を解決しません...

別の例を次に示します。ローカルモードのChefを使用してイメージを構築します。 これを行うには、chefdkを基本イメージに配置し、レシピをマウントまたは追加し、これらのレシピを起動して、イメージを構成し、新しいコンポーネント、パッケージ、構成ファイルなどをインストールします。 同様に、Ansibleなどの別の構成管理システムを使用できます。 ただし、インストールされたchefdkのサイズは約500 MBであり、最終的なイメージのサイズが大幅に増加します。

ただし、Dockerのマルチステージビルドでは、 この問題は解決されません 。 ユーザーがプログラムの副作用、特に作成されるファイルを知りたくない場合はどうすればよいですか? たとえば、画像からエクスポートされたすべてのパスの不必要な明示的な説明を保持しないため。 プログラムを実行し、イメージで何らかの結果を取得したいだけですが、プログラムとその作業に必要なすべての環境が最終イメージの外残るようにします

chefdkの場合、このchefdkを含むディレクトリをビルド時にビルドイメージにマウントすることができます。 ただし、このソリューションには問題があります。

  1. アセンブリに必要なすべてのプログラムが個別のディレクトリにインストールされるわけではなく、アセンブリイメージに簡単にマウントできます。 Ansibleの場合、システムPythonと競合しないようにPythonを非標準の場所にマウントする必要があります。これは既に問題を引き起こしている可能性があります。
  2. マウントされたプログラムは、使用されている基本イメージに依存します。 プログラムがUbuntu向けにビルドされている場合、Alpineなどの、意図されていない環境では起動しない可能性があります。 すべての依存関係を備えたオムニバスパッケージであるchefdkでさえ、依然としてシステムglibcに依存しており、musl libcを使用するAlpineでは動作しません。

しかし、すべての有用なユーティリティの静的な不変のセットを準備できたら、それは非常に巧妙にリンクされて、基本イメージ 、スクラッチでも動作しますか? そのような/そのようなイメージをベースに接続した後、最終イメージには、これらのユーティリティが接続された空のマウントポイントディレクトリのみが存在します。

冒険を探して


理論


/myutilsなど、静的に定義されたいくつかの非標準ディレクトリに一連のプログラムを含むイメージを取得する必要があります。 /myutils内のプログラムは、 /myutils内のライブラリのみに依存する必要があります。

Linuxで動的にコンパイルされるプログラムは、システム上のld-linuxリンカーの場所に依存します。 たとえば、 ubuntu:16.04 bashバイナリは、リンカー/lib64/ld-linux-x86-64.so.2依存するようにコンパイルされます。

 $ ldd /bin/bash linux-vdso.so.1 => (0x00007ffca67d8000) libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007fd8505a6000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fd8503a2000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd84ffd8000) /lib64/ld-linux-x86-64.so.2 (0x00007fd8507cf000) 

さらに、この依存関係は静的であり、バイナリ自体にコンパイルされます。

 $ grep "/lib64/ld-linux-x86-64.so.2" /bin/bash Binary file /bin/bash matches 

したがって、a)条件付き/myutils/bin/bashをコンパイルして、リンカー/myutils/lib64/ld-linux-x86-64.so.2使用する/myutils/lib64/ld-linux-x86-64.so.2ます。 b) /myutils/lib64/ld-linux-x86-64.so.2リンカーは、 /myutils/{lib64,lib}からライブラリを動的にリンクするように構成されていること

最初のステップは、非標準のルートディレクトリにある他のプログラムのアセンブリとその後の作業に必要なすべてを含むtoolchainイメージを構築することです。 これを行うには、 Linux From Scratchプロジェクトの指示に役立ちます。


dappdepsディストリビューションをビルドします


「分布」の画像セットがdappdepsと呼ばれるのはなぜですか? これらの画像はdappコレクターによって使用されるため、このプロジェクトのニーズに合わせて組み立てられます。

したがって、 最終目標は次のとおりです。


Dappdeps画像は互いに依存する場合があります 。 たとえば、dappdeps / baseをビルドするには、dappdeps / toolchainイメージのツールチェーンとglibcが必要です。 dappdeps / baseのすべてのユーティリティをコンパイルした後、dappdeps / toolchainのファイルはランタイムで実行する必要があります。

主な条件は、これらのイメージのユーティリティが非標準の場所 、つまり/.dapp/deps/にあり、標準システムパスのユーティリティやライブラリ依存しないことです。 また、dappdepsイメージには/.dapp/deps以外のファイルを含めないで/.dapp/deps

このようなイメージを使用すると、ユーティリティを含むボリュームでそれらに基づいてコンテナを作成し、Dockerの--volumes-fromオプションを使用して他のコンテナにマウントできます。

dappdeps /ツールチェーンの収集


Linux From Scratchマニュアルの第5章「一時システムの構築」では、主なターゲットディストリビューションを組み立てるユーティリティセットを備えた/toolsで一時chroot環境を構築するプロセスについて説明しています。

この場合、chroot環境のディレクトリをわずかに変更します。 --prefixパラメーターでは、コンパイル時に/.dapp/deps/toolchain/0.1.1を指定します。 これは、dappdeps / toolchainがアセンブリコンテナにマウントされたときにアセンブリコンテナに表示されるディレクトリです。必要なすべてのユーティリティとライブラリが含まれています。 必要なのは、GNU binutils、GCC、およびglibcです。

イメージは、Dockerマルチステージビルドを使用して収集されます。 ubuntu:16.04基づくイメージubuntu:16.04 、環境全体が準備され、プログラムがコンパイルされ、 /.dapp/deps/toolchain/0.1.1 . /.dapp/deps/toolchain/0.1.1インストールされ/.dapp/deps/toolchain/0.1.1 。 次に、このディレクトリをスクラッチイメージdappdeps / toolchain:0.1.1にコピーします。 Dockerfileはここにあります

最終的なdappdeps /ツールチェーンイメージは、LFSの用語で「一時的なシステム」です。 このシステムのGCCは依然としてライブラリへのシステムパスに関連付けられていますが、GCCがベースイメージで動作することを保証しません。 dappdeps / toolchainイメージは補助的なもので、後で使用されます。 ビルドすることは、すでにプログラムの一般的なシステムライブラリとは本当に独立しています。

Omnibusをdappdeps /ツールチェーンで使用する



OmnibusはchefdkGitLabなどのプロジェクトを構築するために使用されます。 プログラムと、システムリンカーとlibcを除くすべての依存ライブラリを使用して、自己完結型のバンドルを作成できます。 すべての手順は、読みやすく便利なRubyレシピで説明されています。 Omnibusプロジェクトには、すでに作成されたオムニバスソフトウェアレシピのライブラリもあります。

それでは、 Omnibusを使用して、残りのdappdeps分布のアセンブリを説明してみましょう。 ただし、システムリンカーとlibcへの依存を取り除くために、dappdeps / toolchainのコンパイラーを使用してOmnibusのすべてのプログラムを収集します。 この場合、プログラムはglibcに関連付けられます。これはdappdeps / toolchainにもあります。

これを行うには、dappdeps / toolchainの内容をアーカイブとして保存します。

 $ docker pull dappdeps/toolchain:0.1.1 $ docker save dappdeps/toolchain:0.1.1 -o dappdeps-toolchain.tar 

Dockerfile ADDディレクティブを使用してこのアーカイブを追加し、アーカイブの内容をアセンブリコンテナーのルートに解凍します。

 ADD ./dappdeps-toolchain.tar /dappdeps-toolchain RUN tar xf /dappdeps-toolchain/**/layer.tar -C / 

オムニバス経由でアセンブリを開始する前に、パス/.dapp/deps/toolchain/0.1.1/binを優先度としてPATH変数に追加し、dappdeps / toolchainのGCCが使用されるようにします。

Omnibus の結果はパッケージ(この例ではDEB)であり、その内容は解凍され、dappdeps / toolchainと同様のDockerマルチステージビルドを使用して/.dapp/deps/{base|gitartifact|...}転送されます。

ビルドdappdeps /ベース


Omnibusのプロジェクトは、 omnibus/config/projects/dappdeps-base.rbプロジェクトファイルomnibus/config/projects/dappdeps-base.rbを使用して説明されています。

 name 'dappdeps-base' license 'MIT' license_file 'LICENSE.txt' DOCKER_IMAGE_VERSION = "0.2.3" install_dir "/.dapp/deps/base/#{DOCKER_IMAGE_VERSION}" build_version DOCKER_IMAGE_VERSION build_iteration 1 dependency "dappdeps-base" 

このファイルには、dappdeps-base Omnibusパッケージとターゲットインストールディレクトリのすべての依存関係が含まれています。 依存関係は、別個のリポジトリー( omn​​ibus-softwareなど )またはomnibus/config/softwareディレクトリーに配置できます。 このディレクトリ内の各ファイルには、パッケージ/コンポーネントのインストール手順が記述されています。 dappdeps-baseの場合、Omnibusは標準のomnibus-softwareリポジトリにないソフトウェアレシピを記述しています: aclattrcoreutilsdiffutilsfindutilsgtarrsyncsedshadowsudotermcap

Omnibusのソフトウェアレシピがどのようなものかを示すrsync例を考えてみましょう。

 name 'rsync' default_version '3.1.2' license 'GPL-3.0' license_file 'COPYING' version('3.1.2') { source md5: '0f758d7e000c0f7f7d3792610fad70cb' } source url: "https://download.samba.org/pub/rsync/src/rsync-#{version}.tar.gz" dependency 'attr' dependency 'acl' dependency 'popt' relative_path "rsync-#{version}" build do env = with_standard_compiler_flags(with_embedded_path) command "./configure --prefix=#{install_dir}/embedded", env: env command "make -j #{workers}", env: env command 'make install', env: env end 

sourceディレクティブは、ソースコードのダウンロード元のURLを示します。 他のコンポーネントへの依存関係は、名前によるdependencyディレクティブによって示されます。 アセンブルされるコンポーネントの名前は、 nameディレクティブによって指定されます。 同様に、各ソフトウェアレシピは、他のコンポーネントへの依存関係を示す場合があります。 buildブロック内には、ソースからの標準ビルドコマンドが示されています。

dappdeps / baseのOmnibusおよびDockerfileプロジェクトは、 ここにあります

dappdeps / gitartifactの収集


dappdeps-gitartifactの場合、Gitビルドレシピのみが必要であり、すでにomnibus-softwareにあります-残っているのは、現在のOmnibusに接続することだけです。 それ以外は、すべて同じです。

dappdeps / gitartifact用のOmnibusおよびDockerfileプロジェクトは、 ここにあります

dappdeps / chefdkの収集


chefdkには、既製のOmnibusプロジェクトが既にあります。 Dockerfileを介してアセンブリコンテナに追加し、標準インストールパスchefdk /opt/chefdk/.dapp/deps/chefdk/2.3.17-2 (インストールパスにはChefバージョンが含まれます)。

dappdeps / chefdkを構築するためのdockerfileは、 ここにあります

dappdepsの収集/ ansible


Ansibleをビルドするために、Pythonインタープリター、pipをインストールし、Ansibleのソフトウェアレシピを説明するOmnibusプロジェクトも開始します。

 name "ansible" ANSIBLE_GIT_TAG = "v2.4.4.0+dapp-6" dependency "python" dependency "pip" build do command "#{install_dir}/embedded/bin/pip install https://github.com/flant/ansible/archive/#{ANSIBLE_GIT_TAG}.tar.gz" command "#{install_dir}/embedded/bin/pip install pyopenssl" end 

ご覧のとおり、Ansibleのイメージは組み込みのPython、pipであり、依存関係のあるpip Ansibleを介してインストールされます。

dappdeps / ansible用のOmnibusおよびDockerfileプロジェクトは、 ここにあります

dappdeps分布の使用方法は?


ボリュームのマウントでdappdepsイメージを使用するには、最初に各イメージのコンテナーを作成し、このコンテナーに保存するボリュームを指定する必要があります。 これが、現時点でDockerに必要なものです。

 $ docker create --name dappdeps-toolchain --volume /.dapp/deps/toolchain/0.1.1 dappdeps/toolchain:0.1.1 no-such-cmd 13edda732176a44d7d822202d8327565b78f4a2190368bb1df46cdad1e127b6e $ docker ps -a | grep dappdeps-toolchain 13edda732176 dappdeps/toolchain:0.1.1 "no-such-cmd" About a minute ago Created dappdeps-toolchain 

コンテナはdappdeps-toolchainと呼ばれます。この名前により、このコンテナの宣言されたすべてのボリュームは、 --volumes-fromを使用して他のコンテナにマウントするために使用できます。 Dockerにはno-such-cmd任意のコマンドパラメーターを指定する必要がありますが、このコンテナーは決して起動されません- Created状態のままになります。

残りのコンテナを作成します。

 $ docker create --name dappdeps-base --volume /.dapp/deps/base/0.2.3 dappdeps/base:0.2.3 no-such-cmd 20f524c5b8b4a59112b4b7cb85e47eee660c7906fb72a4935a767a215c89964e $ docker create --name dappdeps-ansible --volume /.dapp/deps/ansible/2.4.4.0-10 dappdeps/ansible:2.4.4.0-10 no-such-cmd cd01ae8b69cd68e0611bb6c323040ce202e8e7e6456a3f03a4d0a3ffbbf2c510 $ docker create --name dappdeps-gitartifact --volume /.dapp/deps/gitartifact/0.2.1 dappdeps/gitartifact:0.2.1 no-such-cmd 2c12a8743c2b238d90debaf066e29685b41b138c10f2b893a815931df866576d $ docker create --name dappdeps-chefdk --volume /.dapp/deps/chefdk/2.3.17-2 dappdeps/chefdk:2.3.17-2 no-such-cmd 4dffe74c49c8e4cdf9d749177ae9efec3bdae6e37c8b6df41b6eb527a5c1d891 

それで、この大騒ぎがすべて考えられたクライマックスに到達しました。 そのため、機能のデモンストレーションとして、Alpineイメージにnginxパッケージとtreeパッケージをインストールし、dappdepsからAnsible / dappdeps / baseからBash経由でansibleを実行します

 $ docker run -ti --name mycontainer --volumes-from dappdeps-toolchain --volumes-from dappdeps-base --volumes-from dappdeps-gitartifact --volumes-from dappdeps-ansible --volumes-from dappdeps-chefdk alpine:latest /.dapp/deps/base/0.2.3/embedded/bin/bash -lc '/.dapp/deps/ansible/2.4.4.0-10/embedded/bin/ansible localhost -m apk -a "name=nginx,tree update_cache=yes"' [WARNING]: Unable to parse /etc/ansible/hosts as an inventory source [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' localhost | SUCCESS => { "changed": true, "failed": false, "msg": "installed nginx tree package(s)", "packages": [ "pcre", "nginx", "tree" ], "stderr": "", "stderr_lines": [], "stdout": "(1/3) Installing pcre (8.41-r1)\n(2/3) Installing nginx (1.12.2-r3)\nExecuting nginx-1.12.2-r3.pre-install\n(3/3) Installing tree (1.7.0-r1)\nExecuting busybox-1.27.2-r7.trigger\nOK: 6 MiB in 14 packages\n", "stdout_lines": [ "(1/3) Installing pcre (8.41-r1)", "(2/3) Installing nginx (1.12.2-r3)", "Executing nginx-1.12.2-r3.pre-install", "(3/3) Installing tree (1.7.0-r1)", "Executing busybox-1.27.2-r7.trigger", "OK: 6 MiB in 14 packages" ] } 

最後のコード-結果のコンテナからイメージを作成し、... dappdepsからは空のマウントポイントディレクトリしかなかったことがわかります。

 $ docker commit mycontainer myimage sha256:9646be723b91daeaf538b7d92bb8844578abc7acd3028394f543e883eeb382bb $ docker run -ti --rm myimage tree /.dapp /.dapp └── deps ├── ansible │ └── 2.4.4.0-10 ├── base │ └── 0.2.3 ├── chefdk │ └── 2.3.17-2 ├── gitartifact │ └── 0.2.1 └── toolchain └── 0.1.1 11 directories, 0 files 


あなたは他に何を夢見ることができますか?

さらなる作業と問題


dappdepsの問題は何ですか?


dappdeps /ツールチェーンのサイズを小さくするには作業が必要です。 これを行うには、ツールチェーンを2つの部分に分割する必要があります。dappdepsで新しいユーティリティを構築するために必要な部分と、これらのユーティリティを実行するためにランタイムにマウントする必要があるglibcなどの基本ライブラリを持つ部分です。

Ansible aptモジュールをdappdeps / ansibleで機能させるには、Ubuntuのpython-aptパッケージの内容を再構築せずにイメージに直接追加する必要がありました。 この場合、aptモジュールはDEBに基づく基本イメージでは問題なく動作しますが、特定のバージョンのglibcが必要です。 apt自体はディストリビューション固有のモジュールであるため、これは受け入れられます。

Dockerfileには何が欠けていますか?


dappdeps / toolchainイメージのボリュームを使用するには、まずこのイメージのアーカイブを作成してから、 Dockerfile ADDディレクティブを使用して別のイメージに追加する必要があります(「dappdeps / toolchainでのOmnibusの使用」セクションを参照)。 Dockerfileの側から見ると、ビルド時に別のイメージのディレクトリをVOLUMEとして単純に接続できる十分な機能がありません。 --volumes-fromオプションの類似物。

結論


アイデアが機能することを確認し、アセンブリ命令でGNUおよびその他のCLIユーティリティを使用し、PythonまたはRubyインタープリターを実行し、AlsibleまたはスクラッチイメージでAnsibleまたはChefを実行できるようにしました。 この場合、アセンブリ命令の作成者は、実行されるコマンドの実行の副作用を知る必要がなく、Dockerマルチステージビルドの場合のように、インポートする必要のあるファイルを明示的にリストします。

この作業の結果は実際にも適用されます。dappは、アセンブリコンテナでdappdepsイメージを使用します。 たとえば、dappdeps / gitartifactのGitはパッチの操作に使用され、Gitユーティリティはすべてのベースイメージで同じ動作を保証します。 ただし、dappがdappdepsを使用する方法は、この記事の範囲外です。

この記事の目的は、アイデアそのものを伝え、実際の実用例でその応用の可能性を示すことでした。

PS説明されているすべてのdappdepsイメージは、hub.docker.comで入手できますdappdeps/toolchain:0.1.1dappdeps/base:0.2.3dappdeps/gitartifact0.2.1dappdeps/ansible:2.4.4.0-10dappdeps/chefdk:2.3.17-2使用できます。

Source: https://habr.com/ru/post/J352432/


All Articles