待機中:dappでのYAMLおよびAnsible(牛なし)のサポート


今年の初めに、CI / CDプロセスをサポートするためのオープンソースユーティリティ(dappバージョン0.25)に十分な機能セットがあり、イノベーションの作業が開始されたと判断しました。 バージョン0.26では、YAML構文が登場し、Ruby DSLがクラシックとして宣言されました(その後、サポートされなくなります)。 次のバージョン0.27では、主な革新はAnsibleを備えたコレクターの外観と考えることができます。 これらの新製品についてさらに詳しく説明するときが来ました。

2019年8月13日更新: dappプロジェクトの名前がwerfに変更され、そのコードがGoに書き換えられ、ドキュメントが大幅に改善されました。


背景


私たちは2年以上dappを開発しており、日常のメンテナンスでさまざまなサイズの多くのプロジェクトを積極的に使用しています。 ユーティリティの最初のバージョンは、 Chefを使用してイメージを構築することを目的として考案されました。 これに、 Rubyがほとんどすべてのエンジニアと開発者に馴染みがあるという事実を追加したとき、dappをRuby gemとして実装するという論理的な決定を下しました。 Dappfile構成をRuby DSLの形式で作成することが適切であると考えられました-近いフィールドであるVagrantからの成功例が知られているため、なおさらそうです。

ユーティリティが開発されると、dappには2番目の専門分野であるKubernetesへのアプリケーション配信が必要であることが理解されるようになりました。 そのため、Helmチャートで作業するモードがあり、エンジニアはGoでYAML構文とテンプレートをマスターしましたが、開発者はHelmにパッチを送信し始めました。 一方では、Kubernetesへの配信はdappの不可欠な部分になり、他方では、GoはDockerおよびKubernetesエコシステムの事実上の標準になっています。 Rubyで書かれたdappは今では見えません:Dockerコードを再利用するのが難しい場合、ユーザーはしばしばアセンブリマシンにRubyを置きたくないだけです。バイナリをダウンロードする方がはるかに簡単で馴染みやすいです。その結果、dapp開発の主な目標は:a)コードベースのGoへの翻訳、b)YAML構文の実装。

さらに、過去数年間、シェフは機械の制御と組み立ての両方の理由で、私たちに合うのをやめてきました。 結局のところ、Ansibleに切り替えることで、DevOpsエンジニアだけでなく、会議で最も頻繁に発生する問題はdappでのAnsibleのサポートでした 。 したがって、3番目の目標はAnsible-collectorの実装でした。

YAML構文


先ほど、 この記事ですでにYAML構文を紹介しましたが、今からさらに詳しく検討します。

アセンブリ構成は、 dappfile.yaml (またはdappfile.yml )で説明できます。 構成処理ステップは次のとおりです。

  1. dappはdappfile.y[a]ml読み取ります。
  2. Goテンプレートエンジンが起動し、最終的なYAMLがレンダリングされます。
  3. レンダリングされた設定はYAMLドキュメントに分割されます( ---改行あり);
  4. 各YAMLドキュメントに最上位にdimgまたはartifact属性が含まれていることが検証されます。
  5. 残りの属性の構成が確認されます。
  6. すべてが正常である場合-最終構成は、指定されたdimg'eyおよびartifact'ovから作成されます。

古典的なDappfileはRuby DSLであるため、環境変数のENV辞書へのアクセス、ループ内のdimgの定義、コンテキスト継承を使用した一般的なビルド命令の定義など、いくつかのプログラミングが可能です。 開発者からそのような機会を奪わないために、Go-templatesのサポートをdappfile.ymlに追加することが決定されました-Helm chart'amと同様です。

ただし、ネストとdimg_groupsを使用してコンテキストの継承を放棄しました。 これは、利便性よりも混乱をもたらしました。 したがって、 dappfile.ymlはYAMLドキュメントの線形配列であり、各ドキュメントはdimgまたは成果物の説明です。

前と同じように、dimgは1でも名前なしでもかまいません。

 dimg: ~ from: alpine:latest shell: beforeInstall: - apk update 

アーティファクトには名前が必要です 現在では、アーティファクトイメージからのファイルのエクスポートではなく、インポート(Dockerfileからのマルチステージ機能と同様)について説明しています。 したがって、ファイルを取得するアーティファクトを指定する必要があります。

 artifact: application-assets ... --- dimg: ~ ... import: - artifact: application-assets add: /app/public/assets after: install - artifact: application-assets add: /vendor to: /app/vendor after: install 

ディレクティブgitgit remoteshellはDSLからYAMLにほとんど「そのまま」切り替えられますが、アンダースコアの代わりにcamelCaseが使用され(Kubernetesのように)、ディレクティブを繰り返す必要はありませんが、配列を指定してパラメーターを結合する必要があります:

 git: - add: / to: /app owner: app group: app excludePaths: - public/assets - vendor - .helm stageDependencies: install: - package.json - Bowerfile - Gemfile.lock - app/assets/* - url: https://github.com/kr/beanstalkd.git add: / to: /build shell: beforeInstall: - useradd -d /app -u 7000 -s /bin/bash app - rm -rf /usr/share/doc/* /usr/share/man/* - apt-get update - apt-get -y install apt-transport-https git curl gettext-base locales tzdata setup: - locale-gen en_US.UTF-8 

利用可能なすべての属性の基本的な説明は、 ドキュメントに記載されています

ドッカーENVおよびLABEL


dappfile.ymlでは、環境変数とラベルを次のように追加できます。

 docker: ENV: <key>: <value> ... LABELS: <key>: <value> ... 

YAMLでは、DappfileおよびDockerfileにあったように、 ENVまたはLABELSを繰り返すことはできません。

テンプレートエンジン


テンプレートを使用して、異なるdimgまたはアーティファクトの全体的なビルド構成を決定できます。 これは、たとえば、変数を使用して共通のベースイメージを簡単に示すことができます。

 {{ $base_image := "alpine:3.6" }} dimg: app from: {{ $base_image }} ... --- dimg: worker from: {{ $base_image }} 

...または定義されたパターンを使用したより複雑なもの:

 {{ $base_image := "alpine:3.6" }} {{- define "base beforeInstall" }} - apt: name=php update_cache=yes - get_url: url: https://getcomposer.org/download/1.5.6/composer.phar dest: /usr/local/bin/composer mode: 0755 {{- end}} dimg: app from: {{ $base_image }} ansible: beforeInstall: {{- include "base beforeInstall" .}} - user: name: app uid: 48 ... --- dimg: worker from: {{ $base_image }} ansible: beforeInstall: {{- include "base beforeInstall" .}} ... 

この例では、 beforeInstallステージの指示の一部が共通部分として定義されており、各dimgにさらに含まれています。

Go /テンプレートの機能の詳細については、テキスト/テン​​プレートモジュールのドキュメントと、標準機能を補完するsprigモジュールのドキュメントを参照してください。

Ansibleのサポート


Ansible-collectorは3つの部分で構成されています。

  1. Python 2.7が置かれているdappdeps / ansibleイメージ。glibcおよびその他のライブラリでコンパイルされ、あらゆるディストリビューションで動作します(特にAlpineに関連)。 Ansibleはすぐそこにインストールされます。
  2. dappfile.yaml Ansibleを使用したdappfile.yamlアセンブリ構文のサポート
  3. ステージ用のコンテナーを起動するdappのビルダー 。 これらのコンテナでは、 dappfile.yml指定されたタスクdappfile.ymlます。 Builderはプレイブックを作成し、それを起動するコマンドを生成します。

Ansibleは多数のリモートホストの管理システムとして開発されているため、開発者はローカルスタートアップに関連するものを無視できます。 たとえば、Chefの場合のように、実行中のコマンドからのリアルタイム出力はありません 。アセンブリには、実行時間の長いコマンドが含まれている場合があります。その出力はリアルタイムで表示すると便利ですが、Ansibleは完了後にのみ出力を表示します。 GitLab CIを介して起動した場合、これはビルドのハングと見なすことができます。

2番目の問題は、Ansibleの一部であるstdoutコールバックでした。 それらの中には「適度に有益」ではありませんでした。 JSON形式の完全な結果を含む冗長な出力、またはホスト名、モジュール名、およびステータスを含むミニマリズムがあります。 もちろん、私は誇張していますが、画像を組み立てるための適切なモジュールは本当にありません。

3番目に遭遇したのは、いくつかのAnsibleモジュールが外部ユーティリティ(怖くない)、Pythonモジュール(さらに怖くない)、およびPythonバイナリモジュール (悪夢!)に依存していることです。 繰り返しますが、Ansibleの作成者は、作成がシステムバイナリとは別に起動されること、たとえば、 userdel/sbinではなく、別のディレクトリのどこかにあることを考慮しませんでした...

バイナリモジュールの問題は、aptモジュールの機能です。 SOライブラリとしてpython-aptモジュールを使用します。 aptモジュールの別の機能は、タスクの実行中に、python-aptのロードが失敗した場合に、このモジュールを含むパッケージをシステムにインストールしようとすることです。

上記の問題を解決するために、生およびスクリプトタスク用の「ライブ」出力が実装されました。 Ansiballzメカニズムなしで起動できます。 また、stdoutコールバックを実装し、 useradduserdelusermodgetentなどのユーティリティアセンブリをdappdeps / ansibleに追加し、python-aptモジュールをコピーする必要がありました。

その結果、dappのAnsibleビルダーはUbuntu、Debian、CentOS、AlpineのLinuxディストリビューションで動作しますが、すべてのモジュールがまだテストされているわけではないため、dappには正確にサポートされるモジュールのリストがあります。 構成でリストにないモジュールを使用する場合、アセンブリは開始されません-これは一時的な措置です。 サポートされているモジュールのリストはここにあります

dappfile.ymlでAnsibleを使用したビルド構成は、 shell構成に似ています。 ansibleキーには必要なステージがリストされ、各ステージにはタスクの配列が定義されています-通常のプレイブックとほぼ同様に、 tasks属性の代わりにステージ名のみが示されます。

 ansible: beforeInstall: - name: "Create non-root main application user" user: name: app comment: "Non-root main application user" uid: 7000 shell: /bin/bash home: /app - name: "Disable docs and man files installation in dpkg" copy: content: | path-exclude=/usr/share/man/* path-exclude=/usr/share/doc/* dest: /etc/dpkg/dpkg.cfg.d/01_nodoc install: - name: "Precompile assets" shell: | set -e export RAILS_ENV=production source /etc/profile.d/rvm.sh cd /app bundle exec rake assets:precompile args: executable: /bin/bash 

例はドキュメントから取得されます

ここで疑問が生じます: dappfile.ymlにタスクのリストしかない場合、他のすべて(トップレベルのプレイブック、インベントリ)がどこにあるのか、話をする牛を有効にする方法と話をしている牛はどこにいるのか(または無効にする方法)? Ansibleの起動方法について説明します。

ビルダー起動を担当します -これは、ステージでDockerコンテナーの起動パラメーターを決定する非常に複雑なコードではありません:環境変数、ansible-playbook起動コマンド、必要なマウント。 また、ビルダーは、いくつかのファイルが生成されるアプリケーションの一時ディレクトリにディレクトリを作成します。


前の例のbeforeInstallステージのタスクのリストは、このようなplaybook.yml変わります。

 --- hosts: all gather_facts: no tasks: - name: "Create non-root main application user" user: name: app ... - name: "Disable docs and man files installation in dpkg" copy: content: | path-exclude=/usr/share/man/* path-exclude=/usr/share/doc/* dest: /etc/dpkg/dpkg.cfg.d/01_nodoc 

Ansible Assemblyアプリケーションの機能


なる


ansible.cfg設定は次のとおりです。

 [become] become = yes become_method = sudo become_flags = -E -H become_exe = path_to_sudo_insdie_dappdeps/ansible_image 

したがって、タスクでは、 become_user: usernameのみを指定してスクリプトを実行するか、ユーザーからコピーします。

コマンドモジュール


Ansibleには、コマンドおよびスクリプトを実行するための4つのモジュール( rawscriptshellおよびcommandます。 rawscriptは、Ansiballzメカニズムなしで実行されます。これはわずかに高速であり、ライブ出力があります。 raw複数行のアドホックスクリプトを実行できます。

 - raw: | mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency:resolve mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests 

確かに、 environment属性はサポートされていませんが、これを回避できます:

 - raw: | mvn -B -f pom.xml -s $SETTINGS dependency:resolve mvn -B -s $SETTINGS package -DskipTests args: executable: SETTINGS=/usr/share/maven/ref/settings-docker.xml /bin/ash -e 

ファイル


この段階では、 gitディレクティブを除き、リポジトリからコンテナにファイルを転送するメカニズムはありません。 さまざまな種類の構成、スクリプト、その他の小さなファイルをイメージに追加するには、コピーモジュールを使用できます。

  - name: "Disable docs and man files installation in dpkg" copy: content: | path-exclude=/usr/share/man/* path-exclude=/usr/share/doc/* dest: /etc/dpkg/dpkg.cfg.d/01_nodoc 

ファイルが大きい場合、 dappfile.yml内に保存しないために、Go-templateおよび.Files.Get関数を使用できます。

  - name: "Disable docs and man files installation in dpkg" copy: content: | {{.Files.Get ".dappfiles/01_nodoc" | indent 6}} dest: /etc/dpkg/dpkg.cfg.d/01_nodoc 

将来的には、ファイルをアセンブリコンテナに接続するためのメカニズムが実装され、大きなinclude*およびバイナリファイルをコピーしやすくなり、 include*またはimport*を使用できるようになりinclude*

テンプレート化


dappfile.yaml Goテンプレートについては既に述べました。 Ansibleはjinja2テンプレートをサポートしており、これら2つのシステムのセパレーターは同じであるため、jinja呼び出しはGo-templateエンジンからエスケープする必要があります。

  - name: "create temp file for archive" tempfile: state: directory register: tmpdir - name: Download archive get_url: url: https://cdn.example.com/files/archive.tgz dest: '{{`{{ tmpdir.path }}`}}/archive.tgz' 

ビルドの問題をデバッグする


タスクを実行すると、何らかのエラーが発生する場合がありますが、画面上のメッセージだけでは理解できない場合があります。 この場合、環境変数ANSIBLE_ARGS="-vvv"指定して開始できます。出力には、タスクのすべての引数と結果のすべての引数があります(json stdoutコールバックの使用と同様)。

状況が明確でない場合は、イントロdapp dimg bulid --introspect-errorモードでアセンブリを開始できます: dapp dimg bulid --introspect-error エラーが発生するとアセンブリが停止し、コンテナ内でシェルが起動します。 エラーの原因となったコマンドが表示され、隣接するターミナルで一時ディレクトリに移動してplaybook.ymlを編集できます。



ゴーゴーゴー


これはdappの開発における3番目の目標ですが、ユーザーの観点からは、インストールの簡素化を除いて、ほとんど変更はありません。 Goのリリース0.26では、 dappfile.yamlパーサーが実装されました。 現在、メインのdapp機能をGoに変換する作業が進行中です。アセンブリコンテナ、ビルダーの起動、Gitとの連携。 したがって、Ansibleモジュールを含むテストでのあなたの助けは不要ではありません。 GitHubで問題が発生するのを待っているか、Telegramのグループdapp_ruにアクセスしてください

2019年8月13日更新: dappプロジェクトの名前がwerfに変更され、そのコードがGoに書き換えられ、ドキュメントが大幅に改善されました。

PS


牛はどうですか? cowsayプログラムはdappdeps / ansibleではなく、使用されるコールバックstdoutはcowsayが含まれるメソッドを呼び出しません。 残念ながら、牛のいないdappでのAnsible(しかし、誰もあなたが問題を作成するのを止めないでしょう)。

PPS


ブログもご覧ください。

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


All Articles