再利用可能なDocker開発環境


これまでのところ、私はWebアプリケーションの文脈での開発のためのDockerについて話していました.今日、私は反対側を見て、コンテナ化がどのように私が図書館で働くのを助けることができるかについて議論したいです.

昔々…。


わが社の最近の議論Evil Martians ) スラックは、私が最終的にこのポストを書くために私を引き起こしました:私の同僚は、複数のノードを管理する新しいツールについて議論しました.同じマシン上のJSバージョンwritten in Rust —ご存知のように、物事は錆で書き直されると涼しくなる😉).
私の最初の考えは次のとおりでした:“なぜ2020年に地球上で、我々はまだすべてのバージョン管理者、RBNV、NVM、ASDF、何が必要ですか?”私はほとんどそれらを使用する喜びを忘れてしまった:私のコンピュータは、これらのツールをもたらすすべての環境汚染なしで簡単に呼吸する.
ツイストをして、モノリシックなパソコン(またはラップトップ)について話しましょう.

私のスライドBetween monoliths and microservices Railsconf話
約1年前、新しいラップトップに切り替えました.私はバックアップや他のタイムマシンの大ファンではないので、私はゼロから快適な作業環境をクラフトする必要がありました.私は通常(Ruby、Golang、Erlang、ノード)を使用するすべてのランタイムをインストールする代わりに、私は実験とすべてのためにDockerと行くことを決めた:アプリケーションとライブラリ、商用およびオープンソースプロジェクト.言い換えれば、私はgitDip .

I decided to use Docker for everything: applications and libraries, commercial and open-source projects.


フェーズ0 :ユニバーサルDockerを構成します。気象研


docker4 devの設定を保持していると考えますRuby on Whales すべての小さなライブラリのためのオーバーヘッドの束です.うん、それは本当だ.それで、私は共有で始めましたdocker-compose.yml 構成、プロジェクトごとのサービスを含んで、それらの間でボリュームを共有する(したがって、私はすべての依存関係を複数回インストールする必要はありませんでした).
コンテナの起動は次のようになります.
docker-compose -f ~/dev/docker-compose.yml \
  run --rm some_service

# we can omit -f if our project is somewhere inside the ~/dev folder: docker-compose tries to find the first docker-compose.yml up the tree
docker-compose run --rm some_service

# finally, using an alias
dcr some_service
それほど悪くない.唯一の問題は、私がDockerの中で新しいプロジェクトを実行するたびに、サービスを定義しなければならなかったということです.私は調査を続けて、再利用可能なDocker Environment(RDE)概念を思いつきました.

フェーズ1 (破棄) : RDE


RDEの考えは完全に必要性を排除することでしたDockerfile and docker-compose.yml ファイルを定義したテンプレートを使用してオンザフライで生成します.
このようにして、私はそれが働くことを想像しました.
# Open the current folder within a Ruby executor
$ rde -e ruby
root@1123:/app#

# Execute a command within a JRuby executor
$ rde run -e jruby -- ruby -e "puts RUBY_PLATFORM"
java
この考えはAに残ったgist 塵を集める.

フェーズ2(現在の):dockerの構成に戻ります。ディップ


複製問題を解決することは、さらに別のツールを構築することなく行うことができることがわかった😉). RDEコンセプトを議論した後Mikhail Merkushin , 我々は、同様の機能を達成することができた実現Dip いくつかの機能を追加すると、
  • 親ディレクトリの設定を検索する~/dip.yml すべてのプロジェクトについて).
  • 設定ディレクトリからカレントディレクトリへの相対パスを含む環境変数を提供しますworking_dir ).
  • これらの機能はv5.0.0 (mishaに感謝)、そして私は新しい可能性を探求し始めた.
    すべての中間の状態を飛ばして、最終的な構成を見てみましょう.
    現在、私~/dip.yml ルビーやデータベースが異なるだけです.
    version: '5.0'
    
    compose:
      files:
        - ./.dip/docker-compose.yml
      project_name: shared_dip_env
    
    interaction:
      ruby: &ruby
        description: Open Ruby service terminal
        service: ruby
        command: /bin/bash
      jruby:
        <<: *ruby
        service: jruby
      'ruby:latest':
        <<: *ruby
        service: ruby-latest
      psql:
        description: Run psql console
        service: postgres
        command: psql -h postgres -U postgres
      createdb:
        description: Run PostgreSQL createdb command
        service: postgres
        command: createdb -h postgres -U postgres
      'redis-cli':
        description: Run Redis console
        service: redis
        command: redis-cli -h redis
    
    ルビーの宝石で仕事をしたいときはいつでもdip ruby プロジェクトのディレクトリからすべてのコマンドを実行します.bundle install , rake ) 容器の中
    ~ $ cd ~/my_ruby_project
    ~/my_ruby_project $ dip ruby:latest
    
    [../my_ruby_project] ruby -v
    ruby 3.0.0dev (2020-10-20T12:46:54Z master 451836f582) [x86_64-linux]
    
    参照してください、任意の手間をかけずにRuby 3を実行することができます🙂
    私が持っている唯一の特別なトリックはdocker-compose.yml これによって、すべてのプロジェクトのために同じコンテナを再使用することができます.PWD ! はい、必要なのはPWD , ホストマシン上のカレントワーキングディレクトリへの絶対パス.ここでは、この神聖な知識をどのように使用しますか
    version: '2.4'
    
    services:
      ruby: &ruby
        command: bash
        image: ruby:2.7
        volumes:
          # That's all the magic!
          - ${PWD}:/${PWD}:cached
          - bundler_data:/usr/local/bundle
          - history:/usr/local/hist
          # I also mount different configuration files
          # for better DX
          - ./.bashrc:/root/.bashrc:ro
          - ./.irbrc:/root/.irbrc:ro
          - ./.pryrc:/root/.pryrc:ro
        environment:
          DATABASE_URL: postgres://postgres:postgres@postgres:5432
          REDIS_URL: redis://redis:6379/
          HISTFILE: /usr/local/hist/.bash_history
          LANG: C.UTF-8
          PROMPT_DIRTRIM: 2
          PS1: '[\W]\! '
          # Plays nice with gemfiles/*.gemfile files for CI
          BUNDLE_GEMFILE: ${BUNDLE_GEMFILE:-Gemfile}
        # And that's the second part of the spell
        working_dir: ${PWD}
        tmpfs:
          - /tmp
      jruby:
        <<: *ruby
        image: jruby:latest
        volumes:
          - ${PWD}:/${PWD}:cached
          - bundler_jruby:/usr/local/bundle
          - history:/usr/local/hist
          - ./.bashrc:/root/.bashrc:ro
          - ./.irbrc:/root/.irbrc:ro
          - ./.pryrc:/root/.pryrc:ro
      ruby-latest:
        <<: *ruby
        image: rubocophq/ruby-snapshot:latest
        volumes:
          - ${PWD}:/${PWD}:cached
          - bundler_data_edge:/usr/local/bundle
          - history:/usr/local/hist
          - ./.bashrc:/root/.bashrc:ro
          - ./.irbrc:/root/.irbrc:ro
          - ./.pryrc:/root/.pryrc:ro
      postgres:
        image: postgres:11.7
        volumes:
          - history:/usr/local/hist
          - ./.psqlrc:/root/.psqlrc:ro
          - postgres:/var/lib/postgresql/data
        environment:
          PSQL_HISTFILE: /usr/local/hist/.psql_history
          POSTGRES_PASSWORD: postgres
          PGPASSWORD: postgres
        ports:
          - 5432
      redis:
        image: redis:5-alpine
        volumes:
          - redis:/data
        ports:
          - 6379
        healthcheck:
          test: redis-cli ping
          interval: 1s
          timeout: 3s
          retries: 30
    
    volumes:
      postgres:
      redis:
      bundler_data:
      bundler_jruby:
      bundler_data_edge:
      history:
    
    ライブラリをビルドするためにPostgreSQLやREDISが必要な場合は、次のようにします.
    # Launch PostgreSQL in the background
    dip up -d postgres
    # Create a database
    dip createdb my_library_db
    # Run psql
    dip psql
    # And, for example, run tests
    dip ruby -c "bundle exec rspec"
    
    同じDockerネットワーク内の他のコンテナとしてのデータベースdocker-compose.yml ) そして、それらの名前を通してアクセス可能ですpostgres and redis ). 私のコードはDATABASE_URL and REDIS_URL , それぞれ.
    いくつかの例を考えよう.

    VSコードの使用


    あなたがVCコードユーザーであるなら、IntelliSenseの力を使いたいならば、あなたはこの方法を結合することができますRemote Containers : ジャストランdip up -d ruby そして、実行中の容器に付けてください!

    ノード。JSの例


    Rubyの例を見てみましょうDocsify ドキュメントサーバー.
    docsifyはJavaScript/ノードです.ドキュメントジェネレータ.私はusing it すべてのオープンソースプロジェクトのために.ノードが必要です.ジェイズアンドザdocsify-cli インストールするパッケージ.しかし、我々は何もインストールしないでください、覚えています?それをDockerに詰めましょう!
    まず、我々のベースノードサービスを宣言しますdocker-compose.yml :
    services:
      # ...
      node: &node
        image: node:14
        volumes:
          - ${PWD}:/${PWD}:cached
          # Where to store global packages
          - npm_data:${NPM_CONFIG_PREFIX}
          - history:/usr/local/hist
          - ./.bashrc:/root/.bashrc:ro
        environment:
          NPM_CONFIG_PREFIX: ${NPM_CONFIG_PREFIX}
          HISTFILE: /usr/local/hist/.bash_history
          PROMPT_DIRTRIM: 2
          PS1: '[\W]\! '
        working_dir: ${PWD}
        tmpfs:
          - /tmp
    
    それはrecommended 非依存のユーザディレクトリにグローバル依存関係を保持する.また、ボリュームに入れることでこれらのパッケージを「キャッシュする」ことを確認したい.
    env varを定義できます.NPM_CONFIG_PREFIX ) ディップ設定で
    # dip.yml
    environment:
      NPM_CONFIG_PREFIX: /home/node/.npm-global
    
    ドキュメントサイトにアクセスするにはDocsify Serverを実行したいので、portsを公開する必要があります.のために別のサービスを定義し、サーバを実行するコマンドを定義しましょう.
    services:
      # ...
      node: &node
        # ...
    
      docsify:
        <<: *node
        working_dir: ${NPM_CONFIG_PREFIX}/bin
        command: docsify serve ${PWD}/docs -p 5000 --livereload-port 55729
        ports:
          - 5000:5000
          - 55729:55729
    
    インストールするdocsify-cli パッケージについては、次のコマンドを実行します.
    dip compose run node npm i docsify-cli -g
    
    コマンドを定義すると、コマンドを少し簡素化できますnode コマンドでdip.yml :
    interaction:
      # ...
      node:
        description: Open Node service terminal
        service: node
    
    ここでは以下の文字を入力できます:dip node npm i docsify-cli -g 🙂
    今すぐdocsifyサーバーを実行するにはdip up docsify プロジェクトのフォルダで.

    アーランの例:ビルドアーティファクトを維持する


    私が共有したい最終的な例はコンパイルされた言語の世界からである.
    前と同様に、サービスを定義しますdocker-compose.yml と対応するショートカットdip.yml :
    # docker-compose.yml
    services:
      # ...
      erlang: &erlang
        image: erlang:23
        volumes:
          - ${PWD}:/${PWD}:cached
          - rebar_cache:/rebar_data
          - history:/usr/local/hist
          - ./.bashrc:/root/.bashrc:ro
        environment:
          REBAR_CACHE_DIR: /rebar_data/.cache
          REBAR_GLOBAL_CONFIG_DIR: /rebar_data/.config
          REBAR_BASE_DIR: /rebar_data/.project-cache${PWD}
          HISTFILE: /usr/local/hist/.bash_history
          PROMPT_DIRTRIM: 2
          PS1: '[\W]\! '
        working_dir: ${PWD}
        tmpfs:
          - /tmp
    
    # dip.yml
    interactions:
      # ...
      erl:
        description: Open Erlang service terminal
        service: erlang
        command: /bin/bash
    
    この設定がRubyのものと異なるのは、私たちが同じpwd 依存関係を保存し、ファイルをビルドするトリック
    REBAR_BASE_DIR: /rebar_data/.project-cache${PWD}
    
    デフォルトを変更する_build マウントされたボリューム内のものへの場所${PWD} 我々は他のプロジェクトとの衝突がないことを保証します.
    これは、ホスト(特にMacOSユーザーにとって有用である)に書くことによってコンパイルをスピードアップするのを助けます.

    ボーナス:複数のファイルを作成


    ディップを使用する1つの利点は、サービスを読み込むための複数のファイルを指定する機能です.それは私たちが自然によってグループサービスを可能にし、すべてを同じにするのを避けますdocker-compose.yml :
    # dip.yml
    compose:
      files:
        - ./.dip/docker-compose.base.yml
        - ./.dip/docker-compose.databases.yml
        - ./.dip/docker-compose.ruby.yml
        - ./.dip/docker-compose.node.yml
        - ./.dip/docker-compose.erlang.yml
      project_name: shared_dip_env
    
    それだ!この例のセットアップはgist . お気軽に使用して、フィードバックを共有する!
    P . S .私は、ローカルマシンに何もインストールしなかった私の最初の計画が失敗したことを認めなければなりませんbrew install ruby (これはフェーズ2よりずっと前だった).
    P . P . S .最近、私はGitHub Codespaces . 私はまだすべての詳細を把握していないが、それは将来的にライブラリ開発のための私の最初の選択肢になる可能性がありますように見える🙂).