ラクスル事業本部のサーバーサイドエンジニアの杉山です。2023年4月に新卒で入社しました。現在は、ラクスルで取り扱う商品を追加するための開発や、運用・保守を行っています。
今回の記事ではラクスルでの CircleCI の運用改善について紹介します。
ラクスルのサービス事情
ラクスルのサービス( raksul.com )は Ruby on Rails と PHP で構築されています。この Rails と PHP のアプリケーションは別々のリポジトリで管理されています。データベースのマイグレーションは Rails アプリケーション側で管理しています。また、フロントエンドの開発には Node.js を使用しています。
このような構成のため、 PHP のアプリケーションでのデータベースが関わるテストのために CI では Rails アプリケーションでマイグレーションを実行する必要があります。
これまでは、 PHP のアプリケーションの CI の実行時間を短縮するために、特定のバージョンの PHP,Ruby,Node.js をインストールした Docker イメージを AWS ECR に配置し CircleCI で使用していました。
課題
しかし、この方法には ひとつの言語のバージョンを上げるだけでもイメージの再ビルドが必要で手間がかかりました。 また、イメージのビルドは頻繁に行う作業ではないため、作業環境の構築に時間がかかるという問題もありました。
これらの問題を解決するために、新しい方法を検討しました。
解決策: CircleCIでのasdfの活用
この問題を解決するため、ローカル開発環境で使われることが多い asdf を CircleCI 上で使うことにしました。
asdf は複数の言語ランタイムを管理できる CLI ツールです。 Ruby は rbenv 、 Node.js は nvm というように言語ごとにあるバージョン管理ツールをひとまとめにできます。それぞれの言語はプラグインとして追加します。
asdf は以下のようなコマンドで使います。
asdf plugin add nodejs # nodejs プラグインを追加 asdf install nodejs latest # nodejs の最新バージョンをインストール asdf global nodejs latest # nodejs の最新バージョンをグローバルに設定
新たな方法では、 asdf の PHP,Ruby,Node.js のプラグインをインストールしたイメージをビルドし、 AWS ECR にプッシュしておきます。.circleci/config.yml
でそれぞれの言語のバージョンを指定し、 CI 実行時に asdf を使って言語のインストールします。
参考までに簡略版の Dockerfile
と .circleci/config.yml
を掲載します。
FROM cimg/base:current-20.04 USER root RUN apt-get update && apt-get -y upgrade # Install dev library RUN apt-get install -y bison gettext libgd-dev libedit-dev libicu-dev libjpeg-dev libonig-dev libreadline-dev libxml2-dev libzip-dev re2c # for asdf-php RUN apt-get install -y patch rustc libssl-dev libyaml-dev zlib1g-dev libgmp-dev libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev uuid-dev # for asdf-ruby RUN apt-get install -y python3 g++ make python3-pip # for asdf-nodejs USER circleci ENV ASDF_VERSION=v0.11.3 RUN git config --global http.sslverify false # avoid git ssl error RUN git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch $ASDF_VERSION --depth=1 ENV PATH /home/circleci/.asdf/bin:/home/circleci/.asdf/shims:$PATH RUN asdf plugin-add php RUN asdf plugin-add ruby RUN asdf plugin-add nodejs RUN git config --global http.sslverify true
version: 2.1 versions: ruby: &ruby_version 3.2.2 node: &node_version 18.16.0 php: &php_version 8.2.7 bundler: &bundler_version 2.4.13 executors: base: docker: - image: ************.dkr.ecr.ap-northeast-1.amazonaws.com/circleci/base:v1 aws_auth: aws_access_key_id: $AWS_ACCESS_KEY_ID aws_secret_access_key: $AWS_SECRET_ACCESS_KEY environment: TZ: Asia/Tokyo commands: install-php: description: "Install PHP" parameters: version: type: string default: *php_version steps: - run: asdf install php <<parameters.version>> - use-php - save_cache: key: asdf-php-v1-<<parameters.version>> paths: - ~/.asdf/installs/php/<<parameters.version>> restore-php-cache: description: "Restore PHP Build Cache" parameters: version: type: string default: *php_version steps: - restore_cache: keys: - asdf-php-v1-<<parameters.version>> use-php: description: "Use PHP" parameters: version: type: string default: *php_version steps: - run: asdf global php <<parameters.version>> - run: asdf reshim php install-node: description: "Install Node.js" parameters: version: type: string default: *node_version steps: - restore_cache: keys: - asdf-nodejs-v1-<<parameters.version>> - run: asdf install nodejs <<parameters.version>> - run: asdf global nodejs <<parameters.version>> - run: asdf reshim nodejs - save_cache: key: asdf-nodejs-v1-<<parameters.version>> paths: - ~/.asdf/installs/nodejs/<<parameters.version>> install-ruby: description: "Install Ruby" parameters: version: type: string default: *ruby_version bundler-version: type: string default: *bundler_version steps: - restore_cache: keys: - asdf-ruby-v1-<<parameters.version>>-<<parameters.bundler-version>> - asdf-ruby-v1-<<parameters.version>>- - run: asdf install ruby <<parameters.version>> - run: asdf global ruby <<parameters.version>> - run: asdf reshim ruby - run: gem install bundler -v <<parameters.bundler-version>> -N - save_cache: key: asdf-ruby-v1-<<parameters.version>>-<<parameters.bundler-version>> paths: - ~/.asdf/installs/ruby/<<parameters.version>> jobs: build-php: executor: base resource_class: "large" steps: - restore-php-cache - install-php test: executor: base steps: - checkout - install-ruby # Ruby を使う処理 - install-node # Node.js を使う処理 - restore-php-cache - use-php # PHP を使う処理 workflows: version: 2 test: jobs: - build-php - test: requires: - build-php
ポイントは、 asdf install
でインストールした言語のバージョンをキャッシュすることです。インストール済みのバージョンの場合は、キャッシュが使われます。これにより、同じバージョンのインストールは一度だけ行えばよくなります。また、 PHP はビルドに時間がかかるため、 build-php
ジョブに切り出しています。 test
ジョブでは build-php
ジョブでビルドした PHP を使うため、 requires
で依存関係を指定しています。
これにより、 PHP,Ruby,Node.js のバージョンを変更する場合は、 .circleci/config.yml
で指定するバージョンを変更するだけで済み、イメージのビルドは不要になりました。なお、キャッシュを使うことで、以前の方法と同程度の速度で CI を実行できています。
まとめ
CI の改善は、ドメイン知識が浅くてもチームの生産性を向上させることができるため、新卒エンジニアにも取り組みやすい課題だと思っています。今後も、 CircleCI の運用をさらに改善して、ラクスルで働くエンジニアをよりラクにしていきたいです。