Slide 1

Slide 1 text

クジラに乗った Ruby Dockerを使って、便利な開発環境の構築 Andrey Novikov, Evil Martians Izumo Ruby meet-up 2023年 11月 11日

Slide 2

Slide 2 text

アンドレイといいます バックエンドエンジニア フロントエンドもできます Ruby, Go, TypeScriptを使っています SQL, Dockerfiles, TypeScript, bash… オープンソースの大ファン ルビージェムをいくつかを作成して、維持しています 1年前ロシアから日本に引っ越しました 大阪の近くに日本のくらしを楽しんでいます 原付を乗っています。スーパーカブです。 ママチャリも乗って、子供を幼稚園に連れていきます。

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

evilmartians.com 🇯🇵 evilmartians.jp 🇯🇵 邪悪な火星人? イービルマーシャンズ!

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

火星で作ったオープンソース Yabeda: Ruby application instrumentation framework Lefthook: git hooks manager AnyCable: a real-time server for Rails and beyound PostCSS: A tool for transforming CSS with JavaScript Imgproxy: Fast and secure standalone server for resizing and converting remote images A Figma plugin that ensures UI text is readable by leveraging the new APCA algorithm Overmind: Process manager for Procfile-based applications and tmux 詳しくは evilmartians.com/oss

Slide 7

Slide 7 text

目次 1. 開発環境って何? 2. 新人エンジニアが来たら 3. Dockerを使って開発環境を構築する 4. Evil Martians流のアプローチ

Slide 8

Slide 8 text

開発環境って何? 1. オペレーティングシステム 2. 依存関係ライブラリー (OpenSSLなど ) 3. ルビー 4. ルビージェム 5. データベース (PostgreSQL/MySQL, Redis) 6. ソースコードエディタ /IDE 7. git 8. …

Slide 9

Slide 9 text

開発環境って何? 1. オペレーティングシステム 2. 依存関係ライブラリー (OpenSSLなど ) 3. ルビー 4. ルビージェム 5. データベース (PostgreSQL/MySQL, Redis) 6. ソースコードエディタ /IDE 7. git 8. …

Slide 10

Slide 10 text

開発環境の種類 1. ローカル 便利ですが、設定は面倒になるかも、 OSによっては問題になるかも 2. 仮想マシン (Vagrantなど ) 重たいです。 RAMをたくさん使います。 3. コンテナ (Dockerなど ) 軽いですが、便利な設定は簡単とは言えないです。 今日の話題です。 4. リモート 高速なインターネットが必要です。遅いと DXが悪くなります。 5. 未来のこと (WebAssembly?) Stackblitzのようなプロジェクトは WebAssemblyでブラウザーの中で全てを実現していますが、 Rubyはまだ使えないみたいんです。

Slide 11

Slide 11 text

初めにローカル 普段、みんなはローカルで環境を設定して開発しています。

Slide 12

Slide 12 text

ローカルの設定の問題 1. Rubyのインストール 古いバージョンは新しい OSではコンパイルされず、新しいバージョンは古い OSではコンパ イルされません。 (OpenSSL?) アプリが古いバージョンで残ってしまうことがあります。 この問題は Rbenvや asdfで解決されていますが、問題がまだありえます。

Slide 13

Slide 13 text

ローカルの設定の問題 2. ジェムのインストール 悪名高い「 Nokogiriのネイティブ拡張機能をコンパイルできない問題」 (現在はなくなって いるはずですが ) PostgreSQL/MySQLの正しいバージョンのクライアントライブラリをローカルにインスト ールする必要があります。

Slide 14

Slide 14 text

ローカルの設定の問題 3. データベース 適切な PostgreSQLのバージョンをインストールする必要がある( productionと同じ) 古すぎたり、新しすぎたりしたらどうなるでしょうか? それとも、標準以外の拡張機能をイ ンストールする必要がある場合は ?

Slide 15

Slide 15 text

プロジェクトの環境設定時間は ? 新しい開発者を雇用しましたが、新しいコンピューターを入手してコーディングを開始できるまでにどれく らいの時間がかかりますか ? 1. 数分 2. 数時間 3. 数日間

Slide 16

Slide 16 text

ローカル開発環境設定のよくある仕方 1. 手順が記載された READMEファイル 長所 : 少なくとも文書があります 短所 : 長くて面倒くさい、見逃しやすい、古いことが多い , 一つの OSだけに対応しています 2. Bash scripts / Makefiles 長所 : 一度に実行できます 短所 : 普段一つの OSだけに対応しています、古いことが多い 3. Docker 長所 : 一度に実行できます、 OSに依存しません、 OSを汚染しません 短所 : 遅くて、不便、毎日使わないと古くなります

Slide 17

Slide 17 text

火星のプロジェクトの特有点 プロジェクトはよく短時間( 1-2週間) できるだけ速く結果を出さないといけません プロジェクトのデータベースや Rubyのバージョンはいろいろ ローカル OSにはたくさんのライブラリをインストールしたくないです。

Slide 18

Slide 18 text

じゃぁ、 Dockerを使いましょう

Slide 19

Slide 19 text

Dockerの開発環境のよくある問題 イメージを再ビルドの必要がよくあります 毎回は時間がかかります。 実行が遅いです 得に MacOSで デバッグは難しいです。 binding.irb をどうやって使いますか? 長いコマンドを入力する必要で、面倒くさい ` ` docker-compose run --rm app bundle exec rails c # 😫

Slide 20

Slide 20 text

解決方法:火星流の Dockerの設定

Slide 21

Slide 21 text

火星流の Dockerの設定の一覧 1. Dockerのイメージは Rubyとライブラリだけを含めています 2. ジェムは別のボリュームにインストールされています 3. データベースも別のボリュームです 4. いろいろな性能の設定 5. dip: 便利なコマンドラインツール 6. dip: シェルとの統合 7. Railsテンプレートでマイグレーションスクリプト

Slide 22

Slide 22 text

原本の記事 : Ruby on Whales: Dockerizing Ruby and Rails development 日本語翻訳 : クジラに乗った Ruby: Evil Martians流 Docker+Ruby/Rails開発環境構築 原本 日本語翻訳

Slide 23

Slide 23 text

火星流の Dockerの設定の歴史 レシピ集付きのマニュアルとして始まりました。 2019年にブログ記事として公開 2022年にマイグレーションスクリプトも追加されました。 このコマンドで、既にある Railsプロジェクトに Docker開発環境を追加できます。 bundle exec rails app:template \ LOCATION='https://railsbytes.com/script/z5OsoB'

Slide 24

Slide 24 text

Dockerの環境の問題解決 : Dockerfile ` ` # syntax=docker/dockerfile:1 ARG RUBY_VERSION ARG DISTRO_NAME=bullseye FROM ruby:$RUBY_VERSION-slim-$DISTRO_NAME ARG DISTRO_NAME # Common dependencies RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=tmpfs,target=/var/log \ rm -f /etc/apt/apt.conf.d/docker-clean; \ echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache; \ apt-get update -qq \ && DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \ build-essential \ gnupg2 \ curl \ less \ git # Install PostgreSQL dependencies ARG PG_MAJOR RUN curl -sSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | \ gpg --dearmor -o /usr/share/keyrings/postgres-archive-keyring.gpg \ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/postgres-archive-keyring.g $DISTRO_NAME-pgdg main $PG_MAJOR | tee /etc/apt/sources.list.d/postgres.list > /dev/null RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=tmpfs,target=/var/log \ apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \ DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \ libpq-dev \ postgresql-client-$PG_MAJOR # Install NodeJS and Yarn ARG NODE_MAJOR ARG YARN_VERSION=latest RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=tmpfs,target=/var/log \ apt-get update && \ apt-get install -y curl software-properties-common && \ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \ echo "deb https://deb.nodesource.com/node_${NODE_MAJOR}.x $(lsb_release -cs) main" | tee /etc/apt/source apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends nodejs RUN npm install -g yarn@$YARN_VERSION # Application dependencies # We use an external Aptfile for this, stay tuned COPY Aptfile /tmp/Aptfile RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=tmpfs,target=/var/log \ apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \ DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \ $(grep -Ev '^\s*#' /tmp/Aptfile | xargs) # Configure bundler ENV LANG=C.UTF-8 \ BUNDLE_JOBS=4 \ BUNDLE_RETRY=3 # Store Bundler settings in the project's root ENV BUNDLE_APP_CONFIG=.bundle # Uncomment this line if you want to run binstubs without prefixing with `bin/` or `bundle exec` # ENV PATH /app/bin:$PATH # Upgrade RubyGems and install the latest Bundler version RUN gem update --system && \ gem install bundler # Create a directory for the app code RUN mkdir -p /app WORKDIR /app # Document that we're going to expose port 3000 EXPOSE 3000 # Use Bash as the default command CMD ["/bin/bash"] Ruby on Whales: Dockerfile

Slide 25

Slide 25 text

Dockerの環境の問題解決 : Dockerfile 中身は長いですが、イメージは非常に薄くて、シンプルです。再ビルド必要はほとんどありません。 含めている : Ruby Node.js ジェムのコンパイルの為のライブラリ PostgreSQLのクライアント rails dbconsole -p を使うために せっかく含めていない : Rubyのジェム NPMのパッケージ データベースのファイル アプリのコード ` ` ` `

Slide 26

Slide 26 text

Dockerのビルド高速化 RUN --mount のドキュメントを参照してください。 Dockerのレイヤーキャッシュをよく効くために、 RUN --mount を使っています。 ` ` # Application dependencies # We use an external Aptfile for this, stay tuned COPY Aptfile /tmp/Aptfile RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=tmpfs,target=/var/log \ apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist- DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recomm $(grep -Ev '^\s*#' /tmp/Aptfile | xargs) ` ` Dockerfile RUN --mount docs

Slide 27

Slide 27 text

docker-compose.yml ` ` x-app: &app build: context: . args: RUBY_VERSION: '3.2.2' PG_MAJOR: '15' NODE_MAJOR: '18' image: example-dev:1.0.0 environment: &env NODE_ENV: ${NODE_ENV:-development} RAILS_ENV: ${RAILS_ENV:-development} tmpfs: - /tmp - /app/tmp/pids x-backend: &backend <<: *app stdin_open: true tty: true volumes: - ..:/app:cached - bundle:/usr/local/bundle - rails_cache:/app/tmp/cache - node_modules:/app/node_modules - packs:/app/public/packs - packs-test:/app/public/packs-test - history:/usr/local/hist - ./.psqlrc:/root/.psqlrc:ro - ./.bashrc:/root/.bashrc:ro environment: &backend_environment <<: *env REDIS_URL: redis://redis:6379/ DATABASE_URL: postgres://postgres:postgres@postgres:5432 WEBPACKER_DEV_SERVER_HOST: webpacker MALLOC_ARENA_MAX: 2 WEB_CONCURRENCY: ${WEB_CONCURRENCY:-1} BOOTSNAP_CACHE_DIR: /usr/local/bundle/_bootsnap XDG_DATA_HOME: /app/tmp/cache YARN_CACHE_FOLDER: /app/node_modules/.yarn-cache HISTFILE: /usr/local/hist/.bash_history PSQL_HISTFILE: /usr/local/hist/.psql_history IRB_HISTFILE: /usr/local/hist/.irb_history EDITOR: vi depends_on: &backend_depends_on postgres: condition: service_healthy redis: condition: service_healthy services: rails: <<: *backend command: bundle exec rails web: <<: *backend command: bundle exec rails server -b 0.0.0.0 ports: - '3000:3000' depends_on: webpacker: condition: service_started sidekiq: condition: service_started sidekiq: <<: *backend command: bundle exec sidekiq postgres: image: postgres:14 volumes: - .psqlrc:/root/.psqlrc:ro - postgres:/var/lib/postgresql/data - history:/usr/local/hist environment: PSQL_HISTFILE: /usr/local/hist/.psql_history POSTGRES_PASSWORD: postgres ports: - 5432 healthcheck: test: pg_isready -U postgres -h 127.0.0.1 interval: 5s redis: image: redis:6.2-alpine volumes: - redis:/data ports: - 6379 healthcheck: test: redis-cli ping interval: 1s timeout: 3s retries: 30 webpacker: <<: *app command: bundle exec ./bin/webpack-dev-server ports: - '3035:3035' volumes: - ..:/app:cached - bundle:/usr/local/bundle - node_modules:/app/node_modules - packs:/app/public/packs - packs-test:/app/public/packs-test environment: <<: *env WEBPACKER_DEV_SERVER_HOST: 0.0.0.0 YARN_CACHE_FOLDER: /app/node_modules/.yarn-cache volumes: bundle: node_modules: history: rails_cache: postgres: redis: packs: packs-test: Ruby on Whales: docker-compose.yml

Slide 28

Slide 28 text

Dockerの環境の問題解決 : ボリューム Rubyのジェム NPMのパッケージ データベースのファイル キャッシュなど さらに、コンテナ内の /tmp とアプリの tmp/pids に tmpfsを利用されています。 volumes: # … - bundle:/usr/local/bundle - rails_cache:/app/tmp/cache - node_modules:/app/node_modules - packs:/app/public/packs - packs-test:/app/public/packs-test # … ` ` ` `

Slide 29

Slide 29 text

Dockerの環境の問題解決 : ボリューム どうしてマウントされたフォルダーじゃない? ファイルシステムへのアクセスよりも高速 (特に MacOSの場合 ) 前にインストールされた gemを再利用可能 ファイル許可に問題はありません 掃除が簡単

Slide 30

Slide 30 text

Dockerの環境の問題解決 : サービス Docker Composeの設定でいろいろなサービスが定義されています。 rails server と rails console には異なるサービスが使われています。例えば、サーバーの起動用には、 公開するポート番号を定義します。 サービスは dipの設定では使われています。 DRYの為、拡張のサービス定義も使われています。 x-app と x-backend ヘルスチェック rails db:migrate は依存するデータベースサービスの準備が整うまで待機するようにします。 ` ` ` ` ` ` ` ` ` `

Slide 31

Slide 31 text

Dockerの環境の問題解決 : 拡張のサービス定義 x-app: &app build: context: . args: # Ruby, Node, PostgreSQL versions image: example-dev:1.0.0 environment: &env NODE_ENV: ${NODE_ENV:-development} RAILS_ENV: ${RAILS_ENV:-development} tmpfs: - /tmp - /app/tmp/pids x-backend: &backend <<: *app stdin_open: true tty: true volumes: # see previous slides environment: &backend_environment <<: *env REDIS_URL: redis://redis:6379/ services: rails: <<: *backend command: bundle exec rails web: <<: *backend command: bundle exec rails server -b 0 ports: - '3000:3000' depends_on: webpacker: condition: service_started webpacker: <<: *app command: bundle exec ./bin/webpack-dev- ports: - '3035:3035' environment: <<: *env

Slide 32

Slide 32 text

Dockerの環境の問題解決 : 設定 # Application configuration to save memory MALLOC_ARENA_MAX: 2 WEB_CONCURRENCY: ${WEB_CONCURRENCY:-1} # Store caches in Docker volumes BOOTSNAP_CACHE_DIR: /usr/local/bundle/_bootsnap XDG_DATA_HOME: /app/tmp/cache YARN_CACHE_FOLDER: /app/node_modules/.yarn-cache # Convenience devtools HISTFILE: /usr/local/hist/.bash_history PSQL_HISTFILE: /usr/local/hist/.psql_history IRB_HISTFILE: /usr/local/hist/.irb_history

Slide 33

Slide 33 text

Dip: 不足している DXを追加し直す dip は Docker Composeに基づいたコマンドラインツールです。 短くて、便利なコマンドを追加できます dip provision というコマンドで、アプリの環境を再構築できます。 シェルとの統合 ` ` dip rails c # 😎 # instead of docker-compose run --rm app bundle exec rails c # 😫 ` ` eval "$(dip console)" rails c # Dockerの中で起動します 😎 github.com/bibendi/dip

Slide 34

Slide 34 text

dip.yml ` ` version: '7.1' # Define default environment variables to pass # to Docker Compose environment: RAILS_ENV: development compose: files: - .dockerdev/compose.yml project_name: example_demo interaction: # This command spins up a Rails container with the required dependencies (such as da # and opens a terminal within it. runner: description: Open a Bash shell within a Rails container (with dependencies up) service: rails command: /bin/bash # Run a Rails container without any dependent services (useful for non-Rails scripts bash: description: Run an arbitrary script within a container (or open a shell without d service: rails command: /bin/bash compose_run_options: [ no-deps ] # A shortcut to run Bundler commands bundle: description: Run Bundler commands service: rails command: bundle compose_run_options: [ no-deps ] # A shortcut to run RSpec (which overrides the RAILS_ENV) rspec: description: Run RSpec commands service: rails environment: RAILS_ENV: test command: bundle exec rspec rails: description: Run Rails commands service: rails command: bundle exec rails subcommands: s: description: Run Rails server at http://localhost:3000 service: web compose: run_options: [service-ports, use-aliases] yarn: description: Run Yarn commands service: rails command: yarn compose_run_options: [ no-deps ] psql: description: Run Postgres psql console service: postgres default_args: anycasts_dev command: psql -h postgres -U postgres 'redis-cli': description: Run Redis console service: redis command: redis-cli -h redis provision: - dip compose down --volumes - dip compose up -d postgres redis - dip bash -c bin/setup Ruby on Whales: DIP config

Slide 35

Slide 35 text

DIPの設定:コマンド interaction: bundle: service: rails command: bundle compose_run_options: [ no-deps ] # Don't launch database for `bundle install` rspec: service: rails environment: RAILS_ENV: test # overrides the RAILS_ENV command: bundle exec rspec rails: service: rails command: bundle exec rails subcommands: s: service: web compose: run_options: [service-ports, use-aliases]

Slide 36

Slide 36 text

DIPの設定:プロビジョニング dip provision の一つのコマンドでは、アプリの開発環境はゼロから再構築されます。 provision: - dip compose down --volumes - dip compose up -d postgres redis - dip bash -c bin/setup ` `

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

結果 プロジェクトの設定は数分で可能 インターネットの速度によっては、数十分かかることもあります。 異なる OSでも同様に動作します 開発環境をリセットは簡単 dip provision でデータベースなどの状態をゼロから作り直せます。 誰でも今すぐ開発し始めることができます。 ` `

Slide 39

Slide 39 text

驚くほど速くアプリを移動できます Docker開発環境に移動することが速い! なれたら、ローカル OSにすべてをインストールするより、 Dockerを使って開発環境を構築するのほうが速くなります。 bundle exec rails app:template \ LOCATION='https://railsbytes.com/script/z5OsoB'

Slide 40

Slide 40 text

試してみよう! OS Docker と Docker Compose Ruby (サポートされているバージョン ) dip ジェム 必要な既にインストールされているソフトウェア bundle exec rails app:template \ LOCATION='https://railsbytes.com/script/z5OsoB' ruby-on-whales repo

Slide 41

Slide 41 text

原本の記事 : Ruby on Whales: Dockerizing Ruby and Rails development 日本語翻訳 : クジラに乗った Ruby: Evil Martians流 Docker+Ruby/Rails開発環境構築 原本 日本語翻訳

Slide 42

Slide 42 text

ありがとうございました! @Envek @Envek @Envek @Envek github.com/Envek @evilmartians @evilmartians @evilmartians_jp (日本語 ) @evil.martians evilmartians.jp 我々のヤバいブログ : evilmartians.com/chronicles! @hachi8833さんが作ってくれた日本語の翻訳があります! このスライドは次のアドレスでご覧できます : envek.github.io/izumorb-kujira-ni-notta-ruby