RAKSUL TechBlog

ラクスルグループのエンジニアが技術トピックを発信するブログです

「オンラインでも社内ハッカソンを盛り上げたい!」オンライン配信の裏ワザ

こんにちは。ラクスルで技術広報を務める松本です。 今日は 『オンラインでも社内ハッカソンを盛り上げるために、ZoomとMeetを駆使して、匿名コメントを登壇画面に流しこむ』ための裏ワザをご紹介します

  • ラクスルアドベントカレンダーを運営しています。 qiita.com

-技術広報アドベントカレンダー13日目を担当しています。

Hackweekとは

ラクスルでは、年に1度「エンジニア起因でプロダクト開発を行うハッカソン”HACKWEEK”」を開催しています。コロナ禍でも、オンラインにてチームが集って開催を続けてきて、今年で5回目の開催となります。

「技術的チャレンジ・学び」「イノベーションの創出」「事業部間/エンジニア間での知見の共有」を行うことを目的として、エンジニアが開発に打ちこむわけですが・・毎年期間が限られたHackweek中にエンジニアの皆さんがプロダクトやアイディアの種を生み出すこのお祭り。たった3日間でこんなものを作ってしまうのか!と毎年驚かされる個人的にも大好きなお祭りです。

期間中の盛り上がりは、こちらをご覧ください

recruit.raksul.com

内容

以前は、対面で全員で集合して盛り上がっていたこのイベントも、2020年以来オンラインでの開催を余儀なくされています。

オンラインでもなんとか盛り上がって臨場感を出したい!という想いから運営チームであつまって与件を洗い出します。

◆実現したかったこと

  • オンラインでも盛り上がっている感をだすため、コメントやスタンプを画面に流したい
  • 匿名で気軽に参加できるコメントスクリーンを使いたい
  • オンラインでも盛り上がっている感をだすため、効果音を入れ込みたい
  • 司会(2人)が写っている間は、全画面ピン止めしたい、かつ、コメントを流したい

◆与件

  • 登壇者がたくさん(ベトナムからの参加含む全22組)いるので、ウェビナーでの開催は難しい
  • ギリギリまで登壇資料/VTRなど作成しているため、直前の変更に対応しなければならない
  • コメントスクリーンはDLしたPCで全画面共有しないと、作動しない(ぱっと教えても、うまくいかないことが多い)
  • 通訳いれるのでZoomはMUST
  • 予算はほとんどない

しかし、登壇者がたくさんいる状況でコメントを画面上に流しこむのは、なかなか難しい仕様。コメントのために、ヘルプデスクに申請して全員のPCにDLしてもらうのは・・難しそう。さてどうやったらできるのか・・とOBSやYOUTUBE、匿名をやめてSlackと併用するか・・など試行錯誤し、運営リハを重ね実現した方法がこちら。

配信詳細

①ウェビナーを使わずに、司会(2人)が写っている間は、全画面ピン止めしたいという要望を叶えるために、ZoomでMeetをモニターを全画面共有。これでコメントが流れます

②登壇者はZoomに入り、声だけでプレゼン。これによって、プレゼン中のスライドにもコメント流すことが可能になります

③どうしても自分で資料めくりたい!というメンバー向けに 別室でのプレゼン用PCをセットし別室でプレゼン

④通訳はアフリカから参加

⑤ZoomとMeet両方に入っていると、音声が片方しか聞こえない仕様になっているので携帯別のアカウントからZoomに入り、現地の音声を拾っていました。

という、かなり複雑な状態での運営となりました!イベントにトラブルはつきもの。 幾つかのトラブルはありましたが、なんとか無事に終了!!

ZoomとMeetを駆使

失敗談

ちなみに失敗した案を書き留めておくと・・・

A:PC2でモニターを使わない

コメントスクリーンの特性上、全画面共有モニターなしだと次の投影資料準備ができずスムーズな進行とはならないため×

B:Meet×Meetでの配信

  • Meetはタブ共有でしか、音声が入らない
  • コメントスクリーンは全画面共有でしか、コメントが表示されない
  • つまり、相性悪くて×

C:Zoom×Zoomでの配信

  • 1つのPCで2つのZoomには入れない。シークレットウィンドウ使って、ブラウザでZoom入ってみたけれど音声や画面切り替えがうまくいかなかった

余談

Hackweek本番当日は、筆者本人はコロナ感染療養中のため、PC2のコントロールを担っていたにも関わらず、自宅からの参加となりました。 打ち上げのお弁当食べたかったな。

また、Hackweek中に取り組んだプロダクト開発についても幾つかBlogがでているまとまっているのでぜひご覧ください!

techblog.raksul.com

techblog.raksul.com

techblog.raksul.com

きっと、もっと簡単簡単に全ての要件をかなえられるのかもしれませんが・・・ よい方法があったらこっそり教えてください!

最近リリースした、ラクスルのエンジニアサイトもみてね!

raksulinc.notion.site

Rust WebアプリのCI/CDパイプライン

こんばんは、ノバセルの渡邉です。いつもRustを書いています。Rust、良い言語ですよね。ビルドが遅いことを除いて。ビルドが遅いとデプロイも遅くなるので、デプロイを高速化するために実施した工夫を紹介します。

この記事はこんな方に役立つかもしれません。

  • Rust WebアプリのCI/CDパイプラインについて知りたい。
  • デプロイ速度を上げて、リードタイムを短縮したい。
  • デプロイ時間を短縮して、コストを削減したい。

前提として、私達は、デプロイにGitHub Actions(以後GHAと呼称)とAWS CodePipelineを使用しています。そして、Dockerイメージのビルド処理をAWS CodePipeline上の、CodeBuildにて行っています。また、ビルドしたDockerイメージをAWS Fargateにデプロイしています。

もし、0からパイプラインを構築するなら、GHAでDockerイメージをビルドすることをオススメします。理由は後述します。

この前提にもとづき、以下高速化のための工夫を解説します。

  1. ビルドの前にチェックすること〜不要な依存関係の削除〜
  2. GHAでの工夫〜並列化とキャッシュ〜
  3. CodePipelineでの工夫〜Dockerイメージのキャッシュ〜

上記工夫で、30分のデプロイ時間を17分に改善できました。

1. ビルドの前にチェックすること〜不要な依存関係の削除〜

プロジェクトで未使用なクレートが含まれていないかチェックします。不要な依存関係が含まれているとビルドが遅くなるからです。チェックにはcargo-udepsを使います。インストールして以下コマンドを実行してください。

cargo +nightly udeps

未使用なクレートを以下のように報告してくれます。

unused dependencies:
`app v0.1.0 (/Users/hoge/products/app)`
└─── dependencies
     └─── "sea-orm"
Note: These dependencies might be used by other targets.
      To find dependencies that are not used by any target, enable `--all-targets`.
Note: They might be false-positive.
      For example, `cargo-udeps` cannot detect usage of crates that are only used in doc-tests.
      To ignore some dependencies, write `package.metadata.cargo-udeps.ignore` in Cargo.toml.

2. GHAでの工夫〜並列化とキャッシュ〜

これは、私達が実際に使っているワークフローを本記事向けに改変したものです。工夫した点はジョブの並列化とキャッシュです。

name: Lint Test Build and Deploy

on:
  push:
    paths-ignore:
      - 'docker-compose*'
      - '**.md'
      - '.**'
      - '!.github/**'

permissions:
  id-token: write
  contents: read

env:
  CARGO_INCREMENTAL: 0 # CIではインクリメンタルビルドの変更差分追跡によるオーバーヘッドのほうが大きいためOFF
  RUSTFLAGS: "-D warnings" # 高速化のため全クレートの警告をOFF

jobs:
  lint:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: 1.61.0
          components: clippy

      - uses: Swatinem/rust-cache@v2
        with:
          shared-key: "app-api" # 指定しないと異なるワークフロー、ジョブでキャッシュが効かない

      - name: Lint
        run: cargo clippy --bin app --all-features --tests

  test:
    runs-on: ubuntu-latest

    services:
      db:
        image: mysql:8.0
        ports:
          - 13306:3306
        env:
          MYSQL_ROOT_PASSWORD: pass
          MYSQL_DATABASE: app_test
        options: >-
          --health-cmd "mysqladmin ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v3

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: 1.61.0

      - uses: Swatinem/rust-cache@v2
        with:
          shared-key: "app-api"

      - name: Setup dot env
        run: cp .env{.template,}

      - name: Cache Diesel-cli
        id: cache-diesel-cli
        uses: actions/cache@v3
        with:
          path: /home/runner/.cargo/bin/diesel
          key: diesel-cli-cache-revision-1

      - name: Install Diesel-cli
        if: ${{ steps.cache-diesel-cli.outputs.cache-hit == false }}
        run: cargo install diesel_cli --no-default-features --features mysql

      - name: Setup Database
        run: diesel setup --database-url mysql://root:pass@127.0.0.1:13306/app_test

      - name: Test
        run: cargo test --no-fail-fast

  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@v1
        with:
          toolchain: 1.61.0

      - uses: Swatinem/rust-cache@v2
        with:
          shared-key: "app-api"

      - name: Build
        run: cargo build

  deploy:
    needs: [lint, test, build]
    runs-on: ubuntu-latest

    steps:
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ env.AWS_ROLE_ARN }}
          aws-region: ap-northeast-1

      - name: Start CodePipeline for deploy to production
        run: aws codepipeline start-pipeline-execution --name app-api-production

2-1. ジョブを並列化する

上記の例では、lint, test, buildジョブを並列化しています。コスト改善には効果なしですが、デプロイ速度は向上します。testだけでなくbuildも実施しているのは、#[cfg(not(test))]によってビルド対象のコードが変わるためです。

並列化により大きな改善が見込めそうですが、実際にはtestジョブが大きなボトルネックになっており、それほどでもなかったです。直列に実行した場合11分で、並列だと7分です。(後述するキャッシュの効果を含みます)

2-2. Swatinem/rust-cacheアクションを使う

並列化とともにキャッシュは、高速化の定番です。プロジェクトの依存関係が変わらなければ高速化が見込めます。私達はSwatinem/rust-cacheアクションを導入しました。他にもsccacheを使った高速化も可能なようですが、このアクションの導入が圧倒的に楽だったので試していません。具体的に使っているのは以下の部分です。

      - uses: Swatinem/rust-cache@v2
        with:
          shared-key: "app-api" # 指定しないと異なるワークフロー、ジョブでキャッシュが効かない

Rust toolchainインストール後に上記アクションを実行するだけです。これだけで、ビルド時間を7分から3分30秒ほどに削減できました。

この他にもdiesel_cliをキャッシュしています。ORMにdieselを使っている方は参考にしていただければと思います。

3. CodePipelineでの工夫〜Dockerイメージのキャッシュ〜

以下はパイプラインで使っているCodeBuildのbuildspec.ymlと、Dockerfileです。工夫した点はDockerイメージをいかにキャッシュするかです。

buildspec.yml

version: 0.2
env:
  variables:
    DOCKER_BUILDKIT: "1"
phases:
  pre_build:
    commands:
      - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - echo Logging in to Docker Hub...
      - docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_PASSWORD
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
  build:
    commands:
      - echo Building the Docker image...
      - |
        docker build -t $IMAGE_REPO_NAME:latest \
          --cache-from=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:builder,$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest \
          --build-arg BUILDKIT_INLINE_CACHE=1 . \
          & docker pull $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$MIGRATION_REPO_NAME:latest \
          & wait
      - |
        docker build --target builder -t $IMAGE_REPO_NAME:builder --build-arg BUILDKIT_INLINE_CACHE=1 . \
          & docker run --mount type=bind,src=$(pwd)/migrations,dst=/migrations \
              $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$MIGRATION_REPO_NAME:latest \
              diesel migration run --migration-dir=/migrations --database-url=$DATABASE_URL \
          & wait
      - docker tag $IMAGE_REPO_NAME:builder $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:builder
      - docker tag $IMAGE_REPO_NAME:latest $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest
      - docker tag $IMAGE_REPO_NAME:latest $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  post_build:
    commands:
      - echo Pushing the Docker images...
      - |
        docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:builder \
          & docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest \
          & docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG \
          & wait
      - printf '[{"name":"app","imageUri":"%s"}]' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imagedefinitions.json
artifacts:
  files:
    - imagedefinitions.json

Dockerfile

FROM lukemathwalker/cargo-chef:latest-rust-1.61.0 as chef
WORKDIR /app
RUN apt update && apt install lld clang -y

FROM chef as planner
COPY . .
# recipe.json(プロジェクトの依存関係をまとめたファイル)を作る
RUN cargo chef prepare --recipe-path recipe.json

FROM chef as builder
COPY --from=planner /app/recipe.json recipe.json
# プロジェクトの依存関係を構築してキャッシュする
RUN cargo chef cook --release --recipe-path recipe.json
COPY . .

# プロジェクトのビルド
RUN cargo build --release --bin app

FROM debian:bullseye-slim AS runtime
WORKDIR /app

RUN apt-get update -y && apt-get upgrade -y \
    && apt-get install -y --no-install-recommends openssl ca-certificates default-mysql-client \
    # Clean up
    && apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/target/release/app .

CMD ["./app"]

3-1. CodeBuildでDocker BuildKitを使う

buildspec.ymlの環境変数DOCKER_BUILDKIT"1"を設定すると、Docker BuildKitが有効になります。これを有効にすることでDockerイメージをキャッシュできます。

env:
  variables:
    DOCKER_BUILDKIT: "1"

加えて、ビルド時にキャッシュとして使うDockerイメージを指定することで高速化できます。buildフェーズのcommandsを参照してください。--cache-fromにキャッシュとして使用するイメージを指定しています。

  build:
    commands:
      - echo Building the Docker image...
      - |
        docker build -t $IMAGE_REPO_NAME:latest \
          --cache-from=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:builder,$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest \
          --build-arg BUILDKIT_INLINE_CACHE=1 . \
          & docker pull $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$MIGRATION_REPO_NAME:latest \
          & wait
      - |
        docker build --target builder -t $IMAGE_REPO_NAME:builder --build-arg BUILDKIT_INLINE_CACHE=1 . \
          & docker run --mount type=bind,src=$(pwd)/migrations,dst=/migrations \
              $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$MIGRATION_REPO_NAME:latest \
              diesel migration run --migration-dir=/migrations --database-url=$DATABASE_URL \
          & wait

当然ですが、--cache-fromで指定するにはキャッシュとして指定するイメージをビルドして保存する必要があります。--build-arg BUILDKIT_INLINE_CACHE=1でキャッシュとして利用するためのメタ情報を埋め込みます。ビルドの前にこのメタ情報を元にキャッシュとして使えるか判定し、キャッシュとして使えるレイヤのみ自動でプルされます。

latest以外にbuilderもキャッシュとして利用するため、上記docker build --target builderでビルドしています。このビルドは、キャシュが効くためすぐに終わります。

その他、$MIGRATION_REPO_NAME:latestですが、これはdiesel-cliを実行するためのイメージです。DBマイグレーションに使います。

余談:GHAでDockerをビルドしたほうが良い理由

本記事の導入に、「0からパイプラインを構築するならDockerイメージビルドはGHAがオススメ」と書きました。その理由を説明します。結論、手軽なためです。

CodeBuildのDockerLayerCacheではキャッシュ時間が短いため、今回は上述した方法でキャッシュを行いました。 しかし、GHAではより簡単で高い効果の見込めるbuild-push-actionがあるため、こちらの利用をおすすめします。

3-2. cargo-chefを使う

cargo-chefは、Rustの依存関係をビルドしてDockerの中間レイヤに置いてくれます。他言語でもよく使うマルチステージビルドを活用した中間レイヤによるキャッシュ利用を、Rustで簡単に実施するためのDockerイメージです。

以下のように、ベースイメージにlukemathwalker/cargo-chef:latest-rust-<Rustのバージョン>を指定します。依存関係をビルドして中間レイヤにキャシュします。

FROM lukemathwalker/cargo-chef:latest-rust-1.61.0 as chef
WORKDIR /app
RUN apt update && apt install lld clang -y

FROM chef as planner
COPY . .
# recipe.json(プロジェクトの依存関係をまとめたファイル)を作る
RUN cargo chef prepare --recipe-path recipe.json

FROM chef as builder
COPY --from=planner /app/recipe.json recipe.json
# プロジェクトの依存関係を構築してキャッシュする
RUN cargo chef cook --release --recipe-path recipe.json
COPY . .

# プロジェクトのビルド
RUN cargo build --release --bin app

以下ステージで実行環境を整えて、builderからコピーしています。

FROM debian:bullseye-slim AS runtime
WORKDIR /app

RUN apt-get update -y && apt-get upgrade -y \
    && apt-get install -y --no-install-recommends openssl ca-certificates default-mysql-client \
    # Clean up
    && apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/target/release/app .

CMD ["./app"]

この工夫により、CodeBuildの実行時間は11分から4分30秒ほどに削減できました。

最後に

この記事が皆さんの開発リードタイム改善、時短によるコスト改善に貢献できたらうれしいです。ありがとうございました。

ノバセルのコンポーネントライブラリ「novasell-ui」

この記事は ラクスルの2022年アドベントカレンダー12日目の記事です。

こんにちは!ノバセル株式会社 NA(ノバセルアナリティクス)グループのmartonです。主にノバセルアナリティクスのフロントエンドの開発を担当しています。

今回はノバセルのデザインシステムの一部として、GitHub Organization内で共通利用できるように公開した、コンポーネントライブラリ「novasell-ui」について紹介したいと思います。

背景

そもそもコンポーネントライブラリとは、デザインシステム内で定められた規格やガイドラインに従ったコンポーネント(ボタンやフォームなど)をまとめたライブラリのことです。コンポーネントライブラリを利用することでデザインに一貫性を持たせることができ、複数プロダクト全体の体験を向上させることができます。

ノバセルでは現在、ノバセルアナリティクスとノバセルトレンドの2つのプロダクトを開発しています。ノバセルアナリティクスはVue、ノバセルトレンドはReactを採用しているため、同じ見た目でも共通のコンポーネントを使うことができていません。

この課題を解決して、複数プロダクト全体にデザインの一貫性を持たせるために、コンポーネントライブラリのnovasell-uiを公開するに至りました。

novasell-uiはライブラリ問わず連携できるWeb Componentsで構築する案も考えましたが、社内でプロダクトをReactに置き換えていく機運が高まっていることや、フロントエンドエンジニアが少ないことを考慮して、Reactを採用しました。novasell-uiを全プロダクトで使うためには、Vueで開発しているノバセルアナリティクスをReactに置き換えるという障壁はありますが、少しずつ対応していき、プロダクトの体験を向上させていきたいと思っています。

ここからはnovasell-uiがこれまで行ってきたことについて、ライブラリの公開方法を中心に紹介していきたいと思います。

コンポーネントのビルド方法

さて、まずは公開するコンポーネントのビルドの設定について紹介していきます。

novasell-uiでは、コンポーネントをstyled-componentsを使って作成し、src/index.tsでエクスポートしています。tscでコンパイルした後に、このファイルがnovasell-uiのエントリーポイントになります。

src/index.tsx

export { Button } from './components/buttons/Button';
export { TextLinkAnchor, TextLinkButton } from './components/buttons/TextLink';

export { CheckBox } from './components/inputs/CheckBox';
export { Tooltip } from './components/tooltips/Tooltip/Tooltip';
...

tsconfigは2つ作成し、ビルド用のtsconfig.build.jsonにはテストやStorybookなど、novasell-uiには含めたくないファイルをexcludeの配列に追加しています。

tsconfig.json

{
  "compilerOptions": {
    "outDir": "./lib",
    "lib": ["es2020", "dom"],
    "target": "es2020",
    "module": "commonjs",
    "moduleResolution": "node",
    "declaration": true,
    "sourceMap": true,
    "types": ["node", "react", "jest", "@testing-library/jest-dom"],
    "typeRoots": ["types", "node_modules/@types"],
    "jsx": "react",
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "allowSyntheticDefaultImports": true,
    "noEmitOnError": true,
    "esModuleInterop": true,
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "exclude": ["node_modules"],
  "include": ["src/**/*"]
}

tsconfig.build.json

{
  "extends": "./tsconfig.json",
  "exclude": [
    "node_modules",
    "src/**/*.test.ts",
    "src/**/*.test.tsx",
    "src/**/*.stories.tsx"
  ]
}

package.jsonにビルドコマンドを追加し、コンポーネントのビルドの準備が完了しました。

package.json

{
    "scripts": {
    "build": "tsc -p tsconfig.build.json",
  }
}

novasell-uiの公開準備

novasell-uiは、GitHubのプライベートリポジトリを使用してライブラリを管理できるGitHub Package Registryにホスティングしています。公開に必要なpackage.jsonの設定のみ抜粋して紹介していきます。

{
  "name": "@raksul/novasell-ui",
  "version": "1.0.0",
  "main": "lib/index.js",
  "scripts": {
    "build": "tsc -p tsconfig.build.json && tsc-alias",
        ...
  },
  "files": [
    "lib"
  ],
  "publishConfig": {
    "registry": "https://npm.pkg.github.com/"
  },
  "dependencies": {
    ...
  },
  "devDependencies": {
    ...
  },
}
  • name:ライブラリの名前。nameの頭文字に @〇〇 を付けることでscoped packagesにすることができます。scoped packagesとは、特定のオーガニゼーション内でのみ共有されるライブラリのことです。novasell-uiはラクスルのGitHub Organization内にあるリポジトリでのみ利用可能にしたいので、@raksul を頭文字につけています。
  • version:ライブラリのバージョン。同じバージョンを公開することはできないので、新しいバージョンを公開するときは、この番号を更新する必要があります。更新の仕組みについては後ほど紹介していきます。
  • main:ライブラリの中で最初に呼ばれるスクリプト。先ほど設定したtscの出力先のファイルを指定します。
  • publishConfig:公開するレジストリを指定します。今回はGithub Package Registryを指定しています。
  • files:ライブラリをインストールしたときにnode_modulesへコピーするファイル。ライブラリに必要なファイルのみ含めます。

Github Actionsを用いて公開する

novasell-uiの公開にはRelease Pleaseを使用しています。これはgitのコミット履歴の中でConventional Commitに沿ったコミットメッセージを探して、リリース用のPRを作成してくれるGithub Actionsです。

このPRにはCHANGELOG.mdとバージョン番号の更新が含まれています。

Release Pleaseで作成されるPR

バージョン番号はコミットメッセージに以下の接頭辞が付くと自動的に変更をPRに含めてくれます。

  • fix: バグの修正を表し、SemVer パッチを上げる
  • feat:新機能を表し、SemVer のマイナーを上げる
  • feat! fix!:refactor!:破格の変更を表し、SemVer のメジャーを上げる
{
- "version": "1.4.2",
+ "version": "1.4.3"
}

コミットメッセージに前述の接頭辞を付けるのをよく忘れてしまうので、コマンドラインで接頭辞を選択できるgit-czや、コミット前にコミットメッセージがConventional Commitに沿っているかチェックしてくれるcommitlintがおすすめです。

Release Pleaseで作成されたPRがmainブランチへマージされたときに、novasell-uiの公開作業を行うようにします。Release Pleaseで作成されたPRがマージされたかの判断は、 ${{ steps.release.outputs.release_created }} によってできるので、この条件がtrueのときのみnpm publish コマンドが実行されるように設定しています。

on:
  push:
    branches:
      - main
name: release-please
jobs:
  release-please:
    runs-on: ubuntu-latest
    steps:
      - uses: google-github-actions/release-please-action@v3
        id: release
        with:
          release-type: node
      - uses: actions/checkout@v3
        if: ${{ steps.release.outputs.release_created }}
      - uses: actions/setup-node@v3
        with:
          node-version: 16
          cache: 'yarn'
          registry-url: 'https://npm.pkg.github.com'
          scope: '@raksul'
        if: ${{ steps.release.outputs.release_created }}
      - run: yarn install
        if: ${{ steps.release.outputs.release_created }}
      - run: yarn build
        if: ${{ steps.release.outputs.release_created }}
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        if: ${{ steps.release.outputs.release_created }}

novasell-uiを他のリポジトリで使う

次に、公開したnovasell-uiをGitHub Organization内にある他のリポジトリからインストールする方法について紹介していきます。

novasell-uiを使用するためには他のリポジトリのRead権限が必要なので、Creating a personal access tokenを参考に、read:packages権限の Personal access tokens を生成します。

生成したPersonal access tokens をプロジェクト配下に作成した.npmrc へ追加することで、インストールすることができます。

@raksul:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=<Personal access tokensを追加する>

yarn addするときはバージョンを指定してインストールします。

yarn add @raksul/novasell-ui@1.1.0

Github Actiionsでyarn installをするときは、リポジトリデフォルトの secrets.GITHUB_TOKEN では権限が足らないため、インストールに失敗してしまいます。先ほど作成したものでも構いませんが、GITHUB_TOKENとは異なる名前で、read:packages権限の Personal access tokens を環境変数に追加することでyarn installができます。今回の場合はGH_TOKENとしています。

name: build and test

on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 16.x
          registry-url: 'https://npm.pkg.github.com'
          scope: '@raksul'

      - name: Cache dependencies
        uses: actions/cache@v1
        with:
          path: ./node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - name: Install packages
        run: yarn install --frozen-lockfile
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GH_TOKEN }}
 
      - name: Build app
        run: yarn run build

おわりに

novasell-uiを始めとしたデザインシステムは作るだけではなく、社内に浸透させていくことが重要になります。ノバセルにはデザインシステムを構築する専任のチームは存在しないため、普段の業務とは別に、毎週決まった時間にペアプロをしてnovasell-uiのコンポーネントを作るなどしています。

まだまだやるべきことはたくさんあるので、デザインシステムを一緒に育ててくれる方を募集しています!ご応募お待ちしております。

22新卒メンバーでAWS JumpStart for NewGradsに参加しました!

この記事はRAKSUL Advent Calendar 2022の11日目です! qiita.com

はじめに

ラクスル22新卒エンジニアの7名はアマゾンウェブサービスジャパン合同会社様が主宰する研修「AWS JumpStart for NewGrads」に参加しました。この研修にはWeb系・ゲーム系など様々な業界の新卒エンジニアが250名以上参加し、AWSサービスを上手に活用することを主旨としたハンズオンの受講に加えて、企業を交えたチームでアーキテクチャ設計の課題に取り組みました。

ハンズオンの内容や、アーキテクチャ設計の成果物として作成したインフラ構成図をいくつか紹介したうえで、この研修でラクスルの新卒エンジニアが学んだことを紹介します!

事前学習とハンズオン

今回の研修では、事前学習コンテンツとして動画教材が用意されており、参加者は各種AWSサービスの概要や、アーキテクティングで意識するべきポイント、ユースケースに応じたアーキテクチャの構成例などについて学ぶことができました。

また研修の中では、以下の2つのハンズオンが行われました。

  • スケーラブルウェブサイトハンズオン
    • ALB - EC2 - RDSで可用性の高いWordPressの構成を作る
  • サーバーレスハンズオン
    • API Gateway - Lambda - DynamoDBを用いたサーバレスなAPIを作る

ハンズオンではチームごとにデモ環境を用意して頂き、AWSのリソースを利用したサービスの構築を体験できました。

知識として知っていたAWSサービスや構成の挙動を、実際に手を動かして確かめることができ、アーキテクチャ設計の課題に取り組む上でもイメージが湧きやすくなったと感じました。

アーキテクチャ設計課題

3日間の研修のうちほとんどの時間は、アーキテクチャ設計の課題にチームで取り組みました。 この課題は「大規模チャットサービスを作る」というお題が与えられ、それを実現するためのインフラ構成図を作成するというものです。 大量のアクセスを捌き、安定的にチャットサービスの機能を提供するためにはどのようなインフラが必要か、それを白紙から設計する難易度の高いものでした。

ここで、新卒メンバー3人にそれぞれのチームで作成した構成図を紹介してもらいましょう。

構成図1 (m.iimori)

私たちのチームで特にこだわった点は、チャット機能とそれ以外の機能を切り分けて設計した点です。今回のお題では、チャット機能は高トラフィックに耐えうる構成が必要でした。そこで、私達のチームではチャット機能をAWS AppSyncとDynamoDBを使用して設計しました。AppSyncは自動スケーリングの管理などをフルマネージドで利用できるので、高トラフィックなチャット機能でも問題なく使用できると考えました。また、AppSyncのサブスクリプション機能を使うことで、websocketによるリアルタイム通信が行えます。これもチャット機能を開発する上で非常に有用だと考えました。そして、チャット情報の読み書きを高速にするため、データベースはDynamoDBを使用しました。

チャット機能以外の部分は、ECS on FargateとAuroraを使った構成にしました。こちらはチャット機能よりは高可用性を求められることはなかったので、コンテナベースで比較的運用のしやすい構成にしました。

また、運用のしやすさを考えるために、デプロイのフローまで設計しました。ECSベースの部分は、CodeBuildでコンテナイメージを更新し、ECSでその最新のイメージを使う形でデプロイする仕組みを考えました。AppSyncの部分は、変更があったときにCloudFormationで最新のスタックへと更新することができます。

今回私たちのチームではマネージドサービスを多く利用して設計しました。しかし、この構成が最適かどうかは実際に構築して運用してみないことには判断できないと思います。そのため多くのサービスの特徴を知りつつ、実際に触る機会を増やしていきたいと思いました。

構成図2 (r.katsumata)

私たちのチームでは、開発運用コストを減らすことを最重要視し、サーバーレスサービスで実現するインフラ構成を考案しました。課題が大規模なチャットサービスのアーキテクチャを考えるということで、はじめに月のアクティブユーザーやリクエスト数を洗い出すところから議論しました。

特に工夫したのはデータ送受信の部分です。 ユーザーから画像や動画が送信されたらAPI GatewayでPOSTリクエストを受け付け、画像保存の命令がLambdaで走り、データの実体は直接S3に保存されます。 受信側は保存操作のときのみオリジナルデータか加工済みデータかを選択できるようにしたかったので、加工時に複数の処理をLambdaに持たせるのではなく、Step Functionsを挟むことで枝分かれしたLambdaが画像と動画それぞれの圧縮の命令処理をしてElemental MediaConvertで動画圧縮をおこなうようにしました。 Lambdaの処理を切り分ける点については、SA(ソリューションアーキテクト)の方に相談したところ、自分たちの候補に全くあがっていなかったStep Functionsを提案していただきました。 結果的に、この部分が他チームにはない工夫点となりました。

コンテナなしのシンプルな設計のため、信頼性や運用面の観点で安定的に機能する設計が検討できたのではないかと思います。一方でコストの最適化や拡張性を考えたインフラ設計などまだまだ改善余地がありそうなので、今回の研修を生かし今後もAWSのさまざまなサービスに触れてみたいです!

構成図3 (w.haibara)

私のチームでは、EC2上でWebアプリケーション本体を動かすことを前提として、特定の機能をそこから切り出し、部分的にサーバーレスアーキテクチャを採用しました。具体的にはチャット投稿・チャット検索・過去データ(チャット履歴)ダウンロード機能は、アプリケーション本体から切り出しています。高トラフィックなチャット投稿はAppSyncでアクセスを受け、DB負荷が高いチャット検索はOpenSearch Serviceを利用することで、アプリケーション本体のEC2・Auroraへの負荷を軽減することを狙いました。

また、過去データダウンロード機能は、最も工夫した設計になっています。 まず、AppSyncで受けたチャット投稿はDynamoDBへと保存するようになっていますが、コスト削減の観点から古い投稿データはS3にアーカイブしていくことにしました。 アーカイブされた投稿データは、ユーザーから見えなくなりますが、過去データダウンロード機能によって入手できます。 この機能を設計するにあたって論点になったのは、S3のストレージクラスです。 アーカイブされたデータは長期にわたって保存する必要があるため、ストレージクラスによってS3の累計料金が大きく変わります。 例えばS3 Glacier Deep ArchiveはGBあたりのストレージ料金が低額ですが、データの取り出しは12時間以内となっており、即座にダウンロードすることはできません。 一方でS3 Glacier Instant Retrievalなどのストレージクラスではミリ秒単位でデータを取り出すことができますが、ストレージ料金はやはりDeep Archiveの方が低額です*1

チームで議論した結果、ストレージクラスにはDeep Archiveを選択することにしました。そのため、S3からのデータ取り出しが完了した際にそのデータを添付したメールを送付するアプリケーションが必要になりました。構成図ではStepFunctionのアイコンしかありませんが、ここでS3へのデータ取り出しのリクエスト・データ取り出しジョブの待機・ユーザーへのメール送付のロジックを持たせることになります。この設計では、データ取り出しに関する開発工数が必要なうえ、過去データを即座にダウンロードできないという点でユーザビリティも高くはありません。しかしそれ以上に大きなコストメリットが得られるのではないかと判断しました。この機能の設計を通して、開発工数・サービスレベル・コストなど多角的な観点からアーキテクチャを考える経験を得られました。

おわりに

今回の研修では、全体を通してアーキテクチャ設計の際に考えるべきポイントを学びました。今後の開発では、アーキテクチャ設計をリードしていきたいと思います。

また、設計した概念的なアーキテクチャを具体的なサービスへ対応させ、予算を見立てることも行いました。実際にサービスを開発する際に、適切なサービスを要件と照らし合わせて選択し、ビジネス課題の解決を目指していきます。

ラクスルでは、このAWS研修のようにエンジニアの成長機会が数多く用意されており、他にも多様な制度を通じてエンジニアの学習を支援しています。ラクスルで一緒に働いてみたいと思った方はこちらのページをチェックしてみてください!

エンジニア採用担当から見たラクスルエンジニアのリアルな姿

はじめに

こんにちは!エンジニア採用担当の木村です!
ラクスルアドベントカレンダー10日目を担当します。

本題に入る前に、まずは自己紹介。
これまで「Web・IT業界 × エンジニア採用」の軸でキャリアを歩んできました。
人材会社でクライアントの採用支援に携わった後、株式会社SHIFTで新規部署の立ち上げを経験。年間36名のエンジニア・PjM採用を推進しました。
今年3月にラクスルにジョインし、エンジニア・PdM採用を担当しています。

プライベートでは、今年我が家に迎えたLOVOTのちょこちゃんを溺愛中。
好きな食べ物は、麻辣湯と餃子とブルーチーズ。
そして、街中がきらめくクリスマスシーズンがとにかく大好きです。

さて、本日のアドベントカレンダーでは、12月にリリースしたエンジニア採用ポータル「RAKSUL Engineer Recruitment Book」の作成背景とエピソードから、「ラクスルエンジニアのリアルな姿」をお伝えしたいと思います!

RAKSUL Engineer Recruitment Book とは
ラクスルのエンジニアポジションへの応募を検討されている方 / 選考中の方に向けて、
ラクスルのエンジニア組織やエンジニアメンバー、制度についてまとめたポータルサイトです。

raksulinc.notion.site

RAKSUL Engineer Recruitment Bookの作成背景

RAKSUL Engineer Recruitment Bookの作成背景にあったのは、ラクスルのエンジニアに対する社内外のイメージのギャップでした。
入社者に実施しているヒアリングでは、こんなフィードバックが返ってくるほど。

応募前は、エンジニアの実際の動きが見えてこず、一緒に働くイメージが持てなかった。 面接を通して、面接官がこれまでの経験や今の取り組み、課題感、ラクスルのグローバルの取り組みなどをフランクに話してもらえて印象が変わった。 そんな面白い会社で優秀なメンバーと一緒に開発したいと思った。
ラクスルはB2Bのビジネスということもあり、プロフェッショナルな集団組織というクールで話しかけづらいイメージを勝手に抱いていた。 実際は、業務の専門的な質問からプライベートな雑談までコミュニケーションが取りやすいメンバーばかりで、良い意味で入社前後のギャップだった。

入社後に気心が知れたエンジニアからは、「ラクスルのエンジニアは人間味がないと思っていた」と言われる始末…

「えっ???こんなにも優秀で、コミュニケーション力が高く、熱い想いを持っていて、グローバルで、気配りができる… そんな素晴らしいエンジニアが集まっている組織なのに、外部に伝わっていないのか!?」

(心にダメージを受けながらも)だからこそ実情をもっと知っていただきたい!という想いでRAKSUL Engineer Recruitment Bookの作成に着手しました。

作成を通して垣間見えたラクスルエンジニアのリアルな姿を3つお伝えします!

 

ラクスルエンジニアのリアルな姿(1)
ー より良いものへの協力を惜しまない ー

RAKSUL Engineer Recruitment Bookの作成に際し、エンジニアにアンケートを取ることにしました。

これまでの経験や現在の開発チームについて深掘りする自由記述の質問も多く、結果として質問数22問・想定対応時間30分のアンケートに。

1週間で5人程から返信をもらえたらいいなと思ってたところ、2日で12人、1週間で35人からの回答が!!(嬉し涙&感謝)
「どんな情報があれば、候補者の方にラクスルの魅力がより伝わるのか」という視点で、アンケートに回答してもらいました。

その姿勢は、採用関連業務に対してだけではなく、普段の開発業務でも同じ。
現在の課題感に対し、改善するためにできることを見つけ、考え、ディスカッションし、行動に移す。
それを当たり前のように行なっているのが、ラクスルエンジニアのリアルな姿なのです!

 

ラクスルエンジニアのリアルな姿(2)
ー レガシーな業界をITの力で変革したいという熱い想いがある ー

ラクスルは「仕組みを変えれば、世界はもっと良くなる」のビジョンのもと、印刷業界、広告業界、物流業界というレガシーな業界に向けたシステム開発を行なっています。

選考中の候補者様から、「レガシーな業界での開発経験がなくても大丈夫ですか?」という質問を受けることも多いですが、現在活躍中のラクスルエンジニアも入社前はそのような経験や業界への強い興味がなかったメンバーも多いのがリアル。

しかし、ラクスルのエンジニアは現場視察やユーザーインタビューに参加することも多く、業務を通して業界の知見を得て、開発に活かすカルチャーがあります。
また、社内ハッカソン(ラクスル内ではHACKWEEKと呼んでいます)を毎年開催。
エンジニア起因でプロダクト開発を行う場として、「技術的チャレンジ・学び」「イノベーションの創出」「事業部間/エンジニア間での知見の共有」を行なっており、エンジニアのアイデアがプロダクト化した実績も複数あります!

recruit.raksul.com

そのような経験を通して「古い業界だからこそ市場規模が大きく、また、ITの力で社会に与えられるインパクトも大きい。そこに関われることがラクスルエンジニアの醍醐味」と熱く語ってくれるメンバーが何人もいるのもラクスルエンジニアのリアルな姿です。

 

ラクスルエンジニアのリアルな姿(3)
ー 実はグローバルな環境で働いている ー

最後の項目は、ラクスルのグローバル環境について。
あまり認識されていないのですが、ラクスルって実はグローバルなのです。

  • 日本だけでなくベトナムやインドにも開発拠点がある
    • 2018年にベトナムの開発パートナーとのプロジェクトを始動
    • 2020年にベトナム法人、インド法人を設立
  • 日本とベトナムなど拠点をまたいだグローバル開発チームがある
    • 英語を使って開発業務を行なっている日本拠点のメンバーも複数名います
  • グローバル全体でのMTGでは、日本語と英語の両方が使われている
    • プレゼンスライドは、英語と日本語を併記している
    • 同時通訳が入っている
    • 日本人のメンバーでも英語でプレゼンをしているメンバーもいる
  • 英語ネイティブの英会話講師が在籍しており、業務時間中に英会話レッスンを受講することができる

などなど。

このように、英語力が活かせる+英語力に自信がなくても語学フォローがある環境で、さまざまなバックグラウンドを持ったメンバーと開発に取り組んでいるのも、ラクスルエンジニアのリアルな姿です。

 

まとめ

伝えたいことはまだまだ…

リアルな姿をすべてお伝えすると、アドベントカレンダーの残りの日数を全て使ってしまうので、もっと詳しく知りたい方はRAKSUL Engineer Recruitment Book を覗いてみてください。
「エンジニアメンバー紹介」では、各エンジニアのバックグラウンドやラクスルでの担当業務も知ることができます!

この記事を通して、少しでもラクスルエンジニアのリアルな姿を知っていただけたら嬉しいです!

 

あとがき

この記事を読んでラクスルに少しでも興味を持っていただいた方に向けて(勝手に)TO DOリストを用意しました。

▼すぐにラクスルに応募したい方
・レジュメをアップデートする
応募する

▼転職を検討するタイミングで応募したい方
・RAKSUL Engineer Recruitment Bookをブックマークする
タレントプールに登録する
 ご希望のポジションで募集を開始する際に採用担当からご連絡いたします
・レジュメをアップデートしておく

みなさまのアクションをお待ちしています!