オンプレサーバの撤去作業をしてきました

こんにちは、taanatsuです。
会社のインフラを支えるサーバをクラウドに移行したため、
データセンターに置いてあるオンプレサーバをお片付けしてきました。

エキサイトは歴史が長いため、オンプレで動かしていたサービスが多々ありました。
加えて社内のVPNなどもオンプレにありました。
しかし、昨今の事情に合わせ全てクラウドに移動!
頑張りました!

というわけで、オンプレが不要になったのでサーバのお片付けをする必要があったのですが、
「データセンターに入る」という体験はなかなかできません。

しかしエキサイトは「好奇心を起点」にし、「得意なことでつながるチームワーク」を大切にするので、
インフラの方々のお手伝いをさせていただくことができました!
そう、エキサイトでは自分のチームだけではなく、チームの垣根を超えて知識をつけることができるのです。

データセンターでは主に、

  • 配線をすべて抜く
  • ラックサーバをすべてサーバラックから抜く

という作業でした。

データセンター内は撮影禁止だったのでその様子はお見せできないのですが、
抜き出したラックサーバはこちらです!(社内で撮影)

ラックサーバの山

この子達が今までのサービスや社内インフラを支えてくれてました。
ありがとう!そしてお疲れ様!

これからはクラウドで頑張っていきます!

というわけで、ちょっとした経験のお話でした。
ではまた、次回!

SpringBootの環境でAzure Container Registryにプッシュする方法

はじめに

エキサイト株式会社 バックエンドエンジニアの山縣です。

既存サービスのリビルドも終盤、作成したアプリケーション(Java / SpringBoot)をデプロイするときがきました。 現在携わっているサービスでは、クラウドサービスにMicrosoft Azureを使用しており、作成したアプリケーションはAzure Container Appsでの運用を考えています。 また、コンテナイメージはAzure Container Registry(ACR)で管理していきます。

本記事では、デプロイ周りを整備しているときに得た知見の1つとして、SpringBootの環境でACRにプッシュする方法について紹介します。

環境

本記事では、jibを使用して、コンテナイメージのビルドとプッシュを行います。 必要に応じて、docker build, docker pushに置き換えて実行してください。 また、自動でデプロイを行うために、GitHub Actionsを使用しています。

  • SpringBoot: 2.6.2
  • jib: 3.1.2

認証情報の取り扱い

認証情報は、ホーム > コンテナー レジストリ > 設定 > アクセスキー から取得します。 そこで取得した認証情報を、リポジトリ > Settings > Secrets > Actionsに設定します。

ログイン処理

下記記事を参考に、Azureへのログイン処理を組み込みました。 azure/docker-login@v1を利用することで、簡単に実装することができます。

github.com

jibでビルド&プッシュ

jibを使用して、SpringBootで作成したアプリケーションをコンテナ化し、ACRにプッシュします。 コマンド1つで、ビルドとプッシュを行ってくれるので非常に便利です。 jibを使用しない場合は、docker build, docker pushと実行する必要があります。

サンプルのドキュメントこちらです。 github.com

全体のワークフロー

次に示すのが全体のワークフローです。 ワークフローを手動実行にするために、workflow_dispathを取り入れました。 詳細はこちらです。

name: Deploy api to dev

on:
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      DOCKER_REPOSITORY: sample.azurecr.io
      DOCKER_NAME: sample-dev

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set Up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Login to ACR
        uses: azure/docker-login@v1
        with:
          login-server: ${{ env.DOCKER_REPOSITORY }}
          username: ${{ secrets.AZURE_CONTAINER_REGISTRY_USERNAME }}
          password: ${{ secrets.AZURE_CONTAINER_REGISTRY_PASSWORD }}

      - name: Build & Push
        run: |
            ./gradlew api:jib \
                -Djib.container.args=--spring.profiles.active=dev \
                -Djib.to.image="${{ env.DOCKER_REPOSITORY }}/${{ env.DOCKER_NAME }}:${{ github.sha }}"

ワークフローを手動実行する

[ Run workflow ] をクリックすると、アプリケーションをコンテナし、ACRにプッシュするようになります。

GitHub Actionsのログを確認します。 ログに表示されているコンテナイメージのタグをACRでも確認します。

Built and pushed image as ***.azurecr.io/sample-devi:29b5e~~~~efe3e3

ACRにプッシュされていることを確認する

ホーム > コンテナー レジストリ > リポジトリ > リポジトリ名 からコンテナイメージを確認します。 コンテナイメージが無事プッシュされています!🎉

おわりに

SpringBootの環境でACRにプッシュする方法について紹介しました。 この方法により、ローカルからコマンドを実行してビルド&プッシュをする必要がなくなります。 この記事がお役に立てれば幸いです。

採用アナウンス

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しています。 また、長期インターンも歓迎しています。

カジュアル面談からもOKです。少しでもご興味がございましたら、お気軽にご連絡頂ければ幸いです。

▼ 募集職種一覧 ▼ www.wantedly.com

【Figmaイベント】Figma Japan Community Eventに行ってきた!

こんにちは、デザイナーの山﨑です。

今回は、本日7/27に開催された「Figma Japan Community Event」のレポについて書こうと思います!

主に会場の様子・ノベルティなどを紹介します。

開場

16:15ごろに入場したところ、なんとかなりの行列が…!

今回のイベントの開催規模は前回のFigma初のオフラインイベント「Config Watch Party」の2倍以上だったので、会場にも沢山の人がいました!

ノベルティ

入り口で名前をチェックした後Figmaノベルティをいただきました!

今回のグッズは扇子・エコバッグ・マスキングテープ・バッヂ・ステッカー・帽子と盛りだくさん!とても可愛いです🥰

Figmaのグッズ!とても可愛い!

会場の様子

メイン会場

前回のConfig Watch Partyよりかなり豪華になっています…!

今回はアメリカからFigmaの主要メンバーが来日されていた事もあり、同時通訳のガイドも完備されていてスムーズにイベントを楽しむことができました!

サテライト会場

りんご飴を配っている…!すごい!

こちらはメイン会場が満杯になった時のサテライト会場です!

開催時期が7月末なので縁日っぽくなっていて素敵でした!Figmaの提灯かわいかったです👏

テラスは貸切になっていて、来場者がのんびりくつろげるようになっていました!

ケータリング

フード・ドリンクを受け取れるカウンター

ケータリングは炭酸水・ブラウニー・ブリトーにしました!

ブリトーは「生ハムとリコッタチーズの白ワイン煮込みのクリームソース」というオシャレすぎる名前と味でした😊

終わりに

最後に、エキサイトではデザイナー、フロントエンジニア、バックエンドエンジニア、アプリエンジニアを絶賛募集しております! 興味があればぜひぜひ連絡ください〜〜!🙇

www.wantedly.com

エキサイトSaaS事業部のエンジニアの働き方を公開します!(2022年7月版)

エキサイト株式会社SaaS事業部の菅間(@sugamaan)です。今回はSaaS事業部のエンジニアが普段どのように働いているかを公開したいと思います。

エキサイト株式会社にはメディア・プラットフォーム・ブロードバンドなど複数の事業部があります。

その中でSaaS事業部は経営管理やファクタリングなどのSaaSを中心とした事業を展開をしています。

今回は、SaaS事業部の中でも自分が所属しているKUROTEN. KUROTEN. for Salesチームに関してご紹介をします。

出社

週2回、月8回出社しています。SaaS事業部は月曜日と木曜日が出社日です。

出社日に通院などの予定が入っている場合は、別日に振替をすることができます。 また、月8回のルールですが、申請を行えば出社頻度を相談することも可能です。

勤務時間

全社的には10:00 ~ 18:30の時間帯で働く人が多いです。

エキサイトはフレックス制度を導入しているため、コアタイムに働けば時間をコントロールすることができます。

コミュニケーションツール

普段のコミュニケーションはSlackとTandemを利用しています。

Slackでは全体チャンネルに加えてtimesチャンネルもあり、各々が好きなことをつぶやいています。

Tandemには「エンジニア部屋」があり、基本エンジニアのメンバーが常駐しています。そのため質問があればいつでも質問できます。

Tandemは全社的にも導入をしているので、他部署のミーティングなどにもリスナーとして参加することができます。

ドキュメント

ドキュメントは全社的にConfluenceを利用しています。

SaaS事業部では月に1回ドキュメント更新Dayを設けており、機能の仕様をまとめています。

KUROTEN.は管理会計に関係するプロダクトなため専門知識がまとまっていると助かります。

タスク管理

タスク管理はJIRAとスプレッドシートを利用しています。

各メンバーが担当しているタスクに関してはスプレッドシートを使い、仕様からスケジュールまでの全情報をまとめています。

全体のスケジュール管理にはJIRAを使っており、週1回15分程度でJIRA会を行って進捗共有をしています。

ミーティング

全メンバー共有のミーティングは以下の通りです。

  • エンジニア定例(週1回・30分)
  • デザイン定例(週1回・30分)
  • 事業部会議(月1回・60分)
  • 全社会議:All Hands Meeting(月1回・60分)

エンジニア定例ではJIRA会やチームが良くなるための提案や相談が行われます。

開発

個人の開発環境はdocker化されており、READMEに従って環境を構築します。 API・フロント・バッチと複数環境がありますが、大体1日あれば環境構築は終了します。

開発の流れはGit-flowに概ね則り開発をしています。 APIやフロントではプルリクエスト時に自動でテストが実行されます。

レビュー

GitHub Actionsでチームの全エンジニアに自動アサインされます。 2LGTMがついたらマージしてOKです。

動作確認・リリース

動作確認はdev環境で行います。 dev環境は1リポジトリに対して1つなため、使う人がslackでコメントを残して利用をします。

デプロイに関してはCodeDeployを使ってデプロイを行います。

チームの意向としてできるだけ自動化しメインの開発に注力したいという気持ちがあるため、Slackへコメントを残す部分なども自動化したいと考えています。

また、デプロイも他事業部で導入しているコメントデプロイ化するなど改善できそうなポイントがあります。

最後に

以上が2022年7月時点でのSaaS事業部の働き方になります。 SaaS事業部は現在急拡大中で、メンバーもどんどん増えています。

そのため、これらのルールもより良く改善をしていく予定です。 エンジニアも積極採用中ですので、是非興味を持った方はカジュアルにお話できると嬉しいです!

Twitter

sugamaa (@sugamaan) / Twitter

募集要項

www.wantedly.com

Flutter初学者がRiverpodを用いてMemoアプリ作ってみた

はじめまして! 今年の6月からエキサイトのアプリエンジニアとして内定者インターンをしております、藤崎裕樹( @ex_naty44 )と申します!

今回はインターンのトレーニングでMemoアプリを作ってみたので、 その過程をまとめてみようと思います。

エキサイトでインターンをする前は他社でweb開発のインターンをしていましたが、モバイルアプリに関する知識はほとんどない状態でした。

そんな僕でもこのメモアプリを作ることが出来たので、今回の記事が初学者の方の参考になれば幸いです。

よろしくお願いします!

目次

  • メモアプリを作成した理由
  • 完成品
  • 要件
  • Riverpodでリプレイス
  • 振り返り

メモアプリを作った理由

Flutterを触ったことがなかったので、まずはUIと基本的な機能についての理解を深めることが主な目的でした。 ゆくゆくはこのエキサイトに入ってくるアプリエンジニアのための参考にしていただければと思い、完成させてみようと考えました。

完成品

メモアプリ完成品1メモアプリ完成品2
メモアプリ完成品

UIとしてはこんな感じです。 右下の+ボタンを押すとメモが投稿出来ます。 右上の選択を押すと、チェックボックスが表示され、 選択したメモをボタンで削除できます。検索フィールドからはメモを検索することができます。

完成品の全機能については以下のリンクから動画で確認できます。

要件

  • 投稿機能
  • 一覧表示
  • 削除機能
  • 投稿日時の保存
  • ローカルストレージでの永続化
  • 編集機能
  • 編集日時の保存
  • 編集日時でのソート
  • 投稿日時でのソート
  • 選択したメモの一括削除
  • 検索機能

これらの機能初めはStatefulwidgetで実装しました。 その後、今後関わりうるプロジェクトでRiverpodが採用されるとのことで、 メモアプリでも導入してみようと思いました。

Riverpodでリプレイス

Riverpodは状態管理パッケージです。 Riverpodではrefオブジェクトを使ってグローバルに定義した様々な種類のProviderを利用することが可能になります。

refオブジェクトを用いるために、 StatefulwidgetとStateをConsumerStateful widgetとConsumerStateに切り替えて、 ProviderとStateNotifireProviderを用いてリプレイスしました(例ではStateProvideを使用しています)。

Stateの変更を検知してUIを再描画する際、今までsetStateで囲んでいましたが、 Providerをref.watchで監視し、変更を検知できるようにしました。 Providerが変更を感知すると、関わっているUIを再描画してくれます。

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // テキストフィールドの値を受け取る
  final _myController = TextEditingController();
  // データ格納用リスト
  final List<String> _items = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Memo App'),
      ),
      body: Column(
        children: [
          // テキストフィールド
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
              controller: _myController,
            ),
          ),
          // リストビュー
          Expanded(
            child: ListView.builder(
              itemCount: _items.length,
              itemBuilder: (_, int index) {
                return Card(
                  child: ListTile(
                    title: Text(_items[index]),
                  ),
                );
              },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _items.add(_myController.text);
          });
          // テキストフィールドの値をクリア
          _myController.clear();
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

上記を以下のように書き変えました。

// ConsumerStatefulWidgetに変更
class MyHomePage extends ConsumerStatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  ConsumerState<MyHomePage> createState() => _MyHomePageState();
}

// StateProviderを定義
final memosProvider = StateProvider.autoDispose((ref) => []);

// ConsumerStateに変更
class _MyHomePageState extends ConsumerState<MyHomePage> {
  final _myController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    // Providerを監視
    final memoList = ref.watch(memosProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Memo App'),
      ),
      body: Column(
        children: [
          // テキストフィールド
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
              controller: _myController,
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: memoList.length,
              itemBuilder: (_, int index) {
                return Card(
                  child: ListTile(
                    title: Text(memoList[index]),
                  ),
                );
              },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          memoList.add(_myController.text);
          // memoListにすると参照渡しになり、変更が検知されない。値渡しにして変更を検知させる。
          ref.read(memosProvider.notifier).state = [...memoList];
          _myController.clear();
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

Riverpodを用いるとグローバルにProviderを利用できるので、大規模なアプリでは非常に便利なので、 これからも色々なプロジェクトでの導入が増えていきそうです!

振り返り

最後に、今回のトレーニングでRiverpodの利点と書き方を学ぶことができました! ですが、まだProviderを全て使い切れた訳ではありません。 このメモアプリ自体もUI、アーキテクチャ等の改善の余地があるので、 今後もアップデートしていきたいと思います!

最後に

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期インターンも歓迎していますので、興味があれば連絡いただければと思います。今回の記事を読んで少しでも興味が湧きましたら是非応募お願い致します!!

就業型インターンの募集情報です! www.wantedly.com

募集職種一覧はこちらになります!カジュアル面談からでも構いません。 www.wantedly.com

SpringBoot x GradleをIntelliJで開発するときは、IntelliJからGradleのタスクを起動する

エキサイト株式会社メディア事業部エンジニアの佐々木です。メディア事業部では、SpringBoot、Gradle、IntelliJでリビルド開発を日々行っています。Dockerを使ったローカル環境での開発を行っています。開発でのSpringBootの起動方法は複数ありますので、3つご紹介します。

前提

スタックは下記になります。

SpringBoot: 2.6.3
Java: 17.0.2
Gradle: 7.3.3

設定

build.gradleに下記の設定を行います。

bootRun {
      sourceResources sourceSets.main  // resourcesディレクトリの参照先を /buildディレクトリではなく、/main配下を参照するようにする
      jvmArgs = [
            '-Dspring.output.ansi.enabled=ALWAYS',   // ログに色付を行う
            '-Dspring.profiles.active=local'   // localのプロファイルを使用する
      ]
}

結論

結論として、IntelliJからGradleタスクを起動しましょう になります。デメリットもとくになく、起動も簡単です。SpringBoot devtoolsを入れれば、コード保存時にビルドが走れば、Hot Restartしてくれます。(Hot Reloadじゃないです)

IntelliJからmainメソッドを使って起動

IntelliJの機能でmainメソッドはエディタ上から起動できます。 これでSpringBootは起動できます。

メリット

デメリット

  • SpringBoot devtools が思ったように動かない
  • Gradleの設定が効いたり効かなかったり(GradleDaemonが増え続けていたときもある)
  • sourceResources sourceSets.main の設定が効かない
    • Thymeleafなどのテンプレートファイルやjs/css等のファイルを更新しても読み込まれない

gradleコマンドから起動

下記コマンドでの起動します。

$ ./gradlew web:bootRun

メリット

  • IntelliJがなくても可能
  • Gradleの設定が効く
  • sourceResources sourceSets.main の設定が効く

デメリット

  • デバッグ機能を使うには追加で設定が必要になる( remote debug の設定が必要になる)

IntellijからGradleタスクで起動

IntelliJに入っているGradleプラグインからSpringBootを起動します。

メリット

  • Gradleの設定が効く
  • sourceResources sourceSets.main の設定が効く

デメリット

いまのところなし

まとめ

起動方法が複数あるので、チーム内であわせていきたいところです。

終わりに

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期インターンも歓迎していますので、興味があれば連絡いただければと思います。

カジュアル面談はこちらになります! meety.net

募集職種一覧はこちらになります!(カジュアルからもOK) www.wantedly.com

jibでコンテナ化するJavaアプリケーションでSIGTERMを無視する方法

こんにちは。 エキサイト株式会社の三浦です。

Javaアプリケーションをコンテナとして動かしたいとき、jibを使うのは大きな選択肢の1つです。

jibを使えば非常に簡単にJavaアプリケーションをコンテナ化することが出来ますが、一方でカスタマイズしたい場合に少し困ってしまう場合もあるのではないでしょうか。

今回はその一例として、jibでコンテナ化するJavaアプリケーションでSIGTERMを無視させる方法を紹介します。

jibとは

jibは、Javaアプリケーションを簡単にコンテナ化してくれるライブラリです。

MavenやGradleでプラグインとして使用できます。

github.com

Jib builds optimized Docker and OCI images for your Java applications without a Docker daemon - and without deep mastery of Docker best-practices. It is available as plugins for Maven and Gradle and as a Java library.

今回は、Gradleで使っていきます。

使い方としては簡単で、 build.gradle

plugins {
  id 'com.google.cloud.tools.jib' version '3.2.1'
}

...

jib {
    from {
        image = 'eclipse-temurin:17.0.2_8-jdk-alpine'
    }
}

こんな形で設定をした後、

./gradlew jibDockerBuild -Djib.to.image=出力イメージ名

を実行すれば、ローカルのDockerデーモンにイメージが作成されます。

JVMの設定をしたい場合などは、以下のように少し設定を加えます。

jib {
    from {
        image = 'eclipse-temurin:17.0.2_8-jdk-alpine'
    }
    container {
        jvmFlags = ['-Duser.timezone=GMT+09', '-Xms1024M', '-Xmx1536M']
    }
}

単にイメージを作りたいだけならこれで良いのですが、今回の「SIGTERMを無視させる」ということをさせたい場合、少し複雑な設定をする必要があります。

jibでコンテナ化するJavaアプリケーションでSIGTERMを無視する方法

こちらで少し説明していますが、今回はSIGTERMを無視するために、dumb-initというライブラリを使用します。

tech.excite.co.jp

このライブラリは受け取ったOSシグナルを別のシグナルに変換してアプリケーションに流すことができるのですが、設定次第で指定したOSシグナルをアプリケーションに流さず落とすことも出来ます。

今回はその設定を使うのですが、その場合、2つ課題があります。

  1. dumb-initを、DockerfileのRUNを使ってダウンロード・インストールする
  2. DockerfileのENTRYPOINTを書き換える

これらは、それぞれ別の方法で解決する必要があります。

jib使用時に、dumb-initをDockerfile内でダウンロード・インストールする

jibでは、Dockerfileにおける RUN を設定できる項目は存在しません。

github.com

Running commands like apt-get slows down the container build process. We do not recommend or support running commands as part of the build.

そのため、jibの設定から RUN を設定するのではなく、すでに RUN を使って dumb-init をインストールし終わったイメージをベースのイメージとして使用する必要があります。

まずは、以下のDockerfileを作成し、適当な名前でイメージを作成します。 (なお、Javaのバージョンは適宜変更してください)

FROM eclipse-temurin:17.0.2_8-jdk-alpine

RUN apk update \
    && apk add --upgrade dumb-init

今回は eclipse-temurin-custom:17.0.2_8-jdk-alpine として作成したとします。

イメージを作成したら、以下のように設定すれば、そのイメージをjibがベースのイメージとして使用してくれます。

jib {
    from {
        image = 'docker://eclipse-temurin-custom:17.0.2_8-jdk-alpine'
    }
    container {
        jvmFlags = ['-Duser.timezone=GMT+09', '-Xms1024M', '-Xmx1536M']
    }
}

まずはこれで、 dumb-init がインストールされたイメージをjibに使わせることが出来ました。

DockerfileのENTRYPOINTを書き換える

続いて、インストールをした dumb-init を、 ENTRYPOINT から使用します。

SIGTERMを無視するには、 ENTRYPOINT で以下のように指定する必要があります。

ENTRYPOINT ["/usr/bin/dumb-init", "--rewrite", "15:0", "--", "以降は実行したいコマンド"]

jibでは、 ENTRYPOINT を書き換える事自体は可能なのですが、少し複雑な設定が必要になります。

github.com

実はjibで ENTRYPOINT を指定すると、先程の例に上げた jvmFlags を含むいくつかの設定が使えなくなってしまうのです。

そこで、例えば

jib {
    from {
        image = 'docker://eclipse-temurin-custom:17.0.2_8-jdk-alpine'
    }
    container {
        jvmFlags = ['-Duser.timezone=GMT+09', '-Xms1024M', '-Xmx1536M']
    }
}

この設定をそのまま使用して dumb-init を使用したい場合、jvmFlags の設定自体も移植して以下のように指定する必要があります。

jib {
    from {
        image = 'docker://eclipse-temurin-custom:17.0.2_8-jdk-alpine'
    }
    container {
        creationTime = 'USE_CURRENT_TIMESTAMP'
        entrypoint = [
                '/usr/bin/dumb-init', '--rewrite', '15:0', '--',
                'java',
                '-Duser.timezone=GMT+09', '-Xms1024M', '-Xmx1536M',
                '-cp', '@/app/jib-classpath-file',
                '@/app/jib-main-class-file'
        ]
    }
}

クラスパス・メインクラスも自分で設定する必要があるので注意しましょう。

なお、クラスパスは /app/jib-classpath-file というファイルに、メインクラスは /app/jib-main-class-file というファイルに自動的に書き出されるので、直接文字列で指定しなくてもそれらを使用すれば問題ないようです。

これでようやく、jibでコンテナ化したJavaアプリケーションがSIGTERMを無視してくれるようになりました。

最後に

jibは非常に便利なライブラリですが、その分複雑なことをしようとすると少し手間がかかる場合があります。

今回の記事が、そういったときに役に立てると幸いです。

【SpringBoot】動的にDBを切り替える方法

はじめに

エキサイト株式会社 バックエンドエンジニアの山縣です。

既存サービスのリビルドをするにあたって、SpringBootで動的にDBを切り替える必要がありました。 これを実現するためにやったことについて本記事で紹介します。

動作環境

  • SpringBoot 2.6.2
  • Java 17
  • MyBatis Spring Boot Starter: 2.1.3
  • SQL Server 2016

複数のDB

現在私が携わっているサービスでは「ユーザーIDのプレフィックス1文字を見てDBの向き先を変更する」というような負荷分散が取り入れられています。 下記図のように、例えばIDが101234567であればDB1を参照し、IDが987654321であればDB9を参照するイメージです。 そのため、アプリケーション側ではIDをもとに動的にDBの向き先を変更する必要がありました。

DataSourceの設定

まずはDBの接続情報をapplication.ymlに記述します。 今回は10台のDBを使用して負荷分散することを想定しているので、10台分の設定が必要です。 例として2台分記述していますが、残り8台のDBの設定情報も同じように記述します。

spring:
  datasource:
    db01:
      url: jdbc:sqlserver://127.0.0.1:1433;databaseName=db01
      username: user1
      password: pass1
      driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
    db02:
      url: jdbc:sqlserver://127.0.0.1:1433;databaseName=db02
      username: user2
      password: pass2
      driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver

次に@ConfigurationPropertiesを使用してDBの接続情報をDataSourceとして扱えるようにします。 上記同様に残り8台分の設定情報は省略しています。

package com.example.db.config;

import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DataSourcePropertiesConfig {
    public static final String DATA_SOURCE_PROPERTIES_DB01 = "dataSourcePropertiesDb01";
    public static final String DATA_SOURCE_PROPERTIES_DB02 = "dataSourcePropertiesDb02";

    public static final String DATA_SOURCE_DB01 = "dataSourceDb01";
    public static final String DATA_SOURCE_DB02 = "dataSourceDb02";

    @Bean(name = {DATA_SOURCE_PROPERTIES_DB01})
    @ConfigurationProperties(prefix = "spring.datasource.db01")
    public DataSourceProperties dataSourcePropertiesDb01() {
        return new DataSourceProperties();
    }

    @Bean(name = {DATA_SOURCE_PROPERTIES_DB02})
    @ConfigurationProperties(prefix = "spring.datasource.db02")
    public DataSourceProperties dataSourcePropertiesDb02() {
        return new DataSourceProperties();
    }

    @Bean(name = {DATA_SOURCE_DB01})
    public DataSource dataSourceDb01(@Qualifier(DATA_SOURCE_PROPERTIES_DB01) DataSourceProperties properties) {
        return properties
                .initializeDataSourceBuilder()
                .build();
    }

    @Bean(name = {DATA_SOURCE_DB02})
    public DataSource dataSourceDb02(@Qualifier(DATA_SOURCE_PROPERTIES_DB02) DataSourceProperties properties) {
        return properties
                .initializeDataSourceBuilder()
                .build();
    }
}

DBの接続先を定義したEnumの作成

DBの接続先を定義したEnumを作成します。 IDのプレフィックス1文字とDBの接続先を対応づけています。

また、IDからDBの接続先のEnumを取得できるようにしており、 DatabaseSchemeType.getSchemeType("312345678")DB03と取得することができるように実装しました。

package com.example.sqlserver.config;

import lombok.Getter;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public enum DatabaseSchemeType {
    DB00("0"),
    DB01("1"),
    DB02("2"),
    DB03("3"),
    DB04("4"),
    DB05("5"),
    DB06("6"),
    DB07("7"),
    DB08("8"),
    DB09("9");

    @Getter
    private final String prefix;

    DatabaseSchemeType(String prefix) {
        this.prefix = prefix;
    }

    private static final Map<String, DatabaseSchemeType> DATABASE_SCHEME_TYPE_MAP = Stream
            .of(DatabaseSchemeType.values())
            .collect(Collectors.toMap(
                    e -> e.getPrefix(),
                    e -> e
            ));

    private static final Pattern ID_PATTERN = Pattern.compile("^([0-9])[0-9]{8}$");

    /**
     * DBスキーマを習得
     *
     * @param id
     * @return DBスキーマ
     */
    public static DatabaseSchemeType getSchemeType(String id) {
        final Matcher matcher = ID_PATTERN.matcher(id);
        if (!matcher.find()) {
            throw new RuntimeException("unexpected id pattern");
        }

        final String prefix = matcher.group(1);
        return DATABASE_SCHEME_TYPE_MAP.get(prefix);
    }
}

DataSourceContextHolderの作成

ThreadLocalを使用してDBの接続先を扱います。 DBに接続する箇所でThreadLocalDatabaseSchemeTypeをセットし、実行が終わったあとにクリアされるようにします。 使い方は後ほど説明します。

package com.example.db.config;

public class DatabaseContextHolder {
    private static final ThreadLocal<DatabaseSchemeType> contextHolder = new ThreadLocal<>();

    public static void setDatabaseSchemeType(DatabaseSchemeType type) {
        contextHolder.set(type);
    }

    public static DatabaseSchemeType getDatabaseSchemeType() {
        return contextHolder.get();
    }

    public static void clear() {
        contextHolder.remove();
    }
}

AbstractRoutingDataSourceを実装する

AbstractRoutingDataSourcedetermineCurrentLookupKeyメソッドを実装することでDataSourceを動的に切り替えることができるようになります。 DatabaseContextHolderからDatabaseSchemeTypeを取得して、それと対応するDataSourceのBean名を返しています。

package com.example.sqlserver.config;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.util.Map;
import java.util.Optional;

import static com.example.sqlserver.config.DataSourceConfig.DATA_SOURCE_DB00;
import static com.example.sqlserver.config.DataSourceConfig.DATA_SOURCE_DB01;
import static com.example.sqlserver.config.DataSourceConfig.DATA_SOURCE_DB02;
import static com.example.sqlserver.config.DataSourceConfig.DATA_SOURCE_DB03;
import static com.example.sqlserver.config.DataSourceConfig.DATA_SOURCE_DB04;
import static com.example.sqlserver.config.DataSourceConfig.DATA_SOURCE_DB05;
import static com.example.sqlserver.config.DataSourceConfig.DATA_SOURCE_DB06;
import static com.example.sqlserver.config.DataSourceConfig.DATA_SOURCE_DB07;
import static com.example.sqlserver.config.DataSourceConfig.DATA_SOURCE_DB08;
import static com.example.sqlserver.config.DataSourceConfig.DATA_SOURCE_DB09;

public class RoutingDataSourceResolver extends AbstractRoutingDataSource {
    /**
     * key: DBスキーマ, value: DataSourceのBean名 のマップ
     */
    private static final Map<DatabaseSchemeType, String> DATA_SOURCE_BEAN_NAME_MAP = Map.of(
            DatabaseSchemeType.DB00, DATA_SOURCE_DB00,
            DatabaseSchemeType.DB01, DATA_SOURCE_DB01,
            DatabaseSchemeType.DB02, DATA_SOURCE_DB02,
            DatabaseSchemeType.DB03, DATA_SOURCE_DB03,
            DatabaseSchemeType.DB04, DATA_SOURCE_DB04,
            DatabaseSchemeType.DB05, DATA_SOURCE_DB05,
            DatabaseSchemeType.DB06, DATA_SOURCE_DB06,
            DatabaseSchemeType.DB07, DATA_SOURCE_DB07,
            DatabaseSchemeType.DB08, DATA_SOURCE_DB08,
            DatabaseSchemeType.DB09, DATA_SOURCE_DB09,
    );

    @Override
    protected Object determineCurrentLookupKey() {
        final DatabaseSchemeType type =DatabaseContextHolder.getDatabaseSchemeType();
        return DATA_SOURCE_BEAN_NAME_MAP.get(type);
    }
}

SqlSessionFactoryを定義する

最後に、作成したRoutingDataSourceResolverを使用してSqlSessionFactoryのBeanを定義します。 これでSpringBootで動的にDBを切り替えるための準備が終わりました。

package com.example.sqlserver.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import static com.example.sqlserver.config.DataSourceConfig.ROUTING_DATA_SOURCE_RESOLVER;
import static com.example.sqlserver.config.RoutingDataSourceResolver.DATA_SOURCE_BEAN_NAME_MAP;

@Configuration
public class SqlSessionFactoryConfig {
    public static final String SQL_SESSION_FACTORY = "sqlSessionFactory";

    @Bean(name = {SQL_SESSION_FACTORY})
    public SqlSessionFactory sqlSessionFactory(@Qualifier(ROUTING_DATA_SOURCE_RESOLVER) RoutingDataSourceResolver resolver) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(resolver);
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();

        // MyBatisの設定を記述

        return sqlSessionFactory;
    }
}

実際に実行する

下記手順で実行すると動的にDBを切り替えることができるようになります。

  1. IDから接続先のEnumを取得する
  2. DatabaseContextHolderに接続先のEnumをセットする
  3. データ取得やデータ追加などのクエリーを実行する
  4. DatabaseContextHolderにセットしたEnumを削除する
package com.examle.repository;

import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class ItemRepositoryImpl implements ItemRepository {

    @Override
    public Item findItemById(String id) {
        final DatabaseSchemeType type = DatabaseSchemeType.getSchemeType(id);
        DatabaseContextHolder.setDatabaseSchemeType(type);

        /* データ取得の処理 */

        DatabaseContextHolder.clear();
    }
}

おわりに

本記事では、SpringBootで動的にDBを切り替える方法について紹介しました。 今回はクエリー実行箇所で毎回DatabaseSchemeTypeをセットする方法について書きましたがこれは非常に面倒です。 そのためAOPを使用して自動で切り替えられるようにすることで手間を省くのがよいと思います。 これについてはまたの機会にまとめる予定です。 最後まで読んでいただき、ありがとうございました!

参考

ECSのデプロイを待つとき、aws ecs wait services-stableだけでは不十分な場合がある

こんにちは。 エキサイト株式会社の三浦です。

ECSにAWS CLIを使ってデプロイするとき、皆さんはデプロイの完了をどうやって検知しますか?

今回は、デプロイ完了検知に aws ecs wait services-stable を使用したときの問題点について説明します。

AWS CLIとECSへのデプロイ

ECSへのデプロイ方法はいくつかありますが、その中の一つにAWS CLIを使用するというものがあります。

aws ecs update-service \
    --cluster クラスタ名 \
    --service サービス名 \
    --task-definition タスク定義のARN

これだけでECSへのデプロイが実現できるので、AWS CLIのインストールやAWS環境への認証さえ行っていれば、非常にお手軽なデプロイ方法になります。

ただし、このコマンドはデプロイは実行してくれるものの、デプロイの完了を待たずにコマンド実行を終了してしまうため、実際にデプロイが完了したかを待つためには別の方法を取る必要があります。

そのために、 aws ecs wait services-stable というコマンドが用意されています。

aws ecs wait services-stable  \
    --cluster クラスタ名 \
    --service サービス名

このコマンドは、実行すると「ECSサービスが安定化」するまで待ち続けてくれるため、 aws ecs update-service 実行後にこのコマンドを実行することで、デプロイを待ってくれます。

これで一見問題ないように見えますが、実は「ECSサービスが安定化」するという定義が少し曲者で、そのせいで aws ecs wait services-stable だけではデプロイを待つのに不十分な場合があります。

ALBを使用したECSへのデプロイでの問題点

aws ecs wait services-stable における「ECSサービスの安定化」とは、以下のような定義になります。

awscli.amazonaws.com

Wait until JMESPath query length(services[?!(length(deployments) == 1 && runningCount == desiredCount)]) == 0 returns True

上記の通り、実はこのコマンドはALBのヘルスチェック等は特に見ておらず、あくまでECS上で deployments の数が1つだけで、 runnningCountdesiredCount の数が同じ、というところしか見ていません。

そのため、例えばALBと接続したECSで「ヘルスチェックの猶予期間」等を長めに指定していたために、ALBのヘルスチェックが通ったかはまだ確定していないものの、ECSの上記のパラメータだけは問題ない数値に落ち着いてしまった場合、 aws ecs wait services-stable は「成功した」として終了してしまうのです。

そういった意味では、残念ながらALBを使用したECSへのデプロイでは、 aws ecs wait services-stable による待機だけでは不十分ということになります。

なにか別の方法で、ALBのヘルスチェックが通ったことを検知する必要があるでしょう。

最後に

aws ecs wait services-stable のこの仕様は、特に不具合というわけではないと思われます。

というのも、ECSは必ずしもALBを使う必要がないサービスであり、ALBを使わないのであれば現在の挙動で十分だからです。

必要に応じて、必要な検知方法を検討・使用して行きましょう。

GitHubのプルリクエストで下線(unserline)を使う

どうも、taanatsuです。 今回はサクッと。

やりたいこと

GitHubのプルリクエストで下線を使い、部分強調したい!
(太文字だと強調が見にくいため)

結論

<ins></ins>で囲む

普通のテキスト
<ins>下線のテキスト</ins>
普通のテキスト

ちなみにはてなだと

<ins>下線のテキスト</ins>

下線のテキスト

↑おおー!ちゃんと下線が引かれますね!
(エディタの下線追加ボタンだと <u> タグが追加されますねー)

終わりに

マークダウンに下線用の記法はないんですね。。。
あと、HTMLの<u></u>だと駄目なんですね。。。

プルリクやissueは見やすくするために、マークダウンを多用しているのでメモがてら!

それでは今回はここまで。
また、次回〜

VPCのサイジングについての失敗談

エキサイト株式会社の武藤です。

AWSVPCのサイジングについて失敗談がありますので、それについて説明します。

オンプレからAWSへシステム移行

担当サービスをオンプレからAWSへ移行していたときのことでした。

インフラチームからVPCのIPレンジを/24(256個)で割り当てられたので、下記のネットワーク構成で移行を進めていました。

VPC : /24 (256個)

AZ : 3AZ

private subnet

  • ap-northeast-1a : /26 (64個)
  • ap-northeast-1c : /26 (64個)
  • ap-northeast-1d : /26 (64個)

public subnet

  • ap-northeast-1a : /28 (16個)
  • ap-northeast-1c : /28 (16個)
  • ap-northeast-1d : /28 (16個)

internet-facing ALBのIPに関する仕様

DBの移行が完了し、アプリケーションサーバの移行に着手していきました。 サーバごとにEC2、internet-facing ALBを構築していたときに、下記のTerraformのエラーが出てしまい、ALBが構築できなくなりました。

│ Error: error creating application Load Balancer: InvalidSubnet: Not enough IP space available in subnet-xxxxxxxxxxxxxxx. ELB requires at least 8 free IP addresses in each subnet.

エラーログから調べてみたところ、internet-facing ALB (ELB) には下記の仕様がありました。

docs.aws.amazon.com

Requirements

For internet-facing load balancers, the subnets that you specify must have at least 8 available IP addresses.

ALB(ELB)には、サブネットに最低8個のIPを確保する必要があり、それを下回る状況となったために起きたエラーでした。

internet-facing のALBはInternet Gatewayがアタッチされたpublic subnetに設置する必要があります。 public subnetには /28 のIPレンジを割り振っていたため、最大で16個のIPがあります。 そして、サブネットごとに5個の予約アドレスが存在します。

docs.aws.amazon.com

各サブネット CIDR ブロックの最初の 4 つの IP アドレスと最後の IP アドレスは使用できず、EC2 インスタンスなどのリソースに割り当てることができません。

つまり、実際に利用可能なIPは11個となります。 ALBを何台か立てていくと、あっという間に8個を下回ってしまいました。

VPCの再構築

その後の対応は、VPCをさらに広いIPレンジで再構築しました。 VPC構成は下記のように変更しました。

VPC : /22 (1024個)

AZ : 3AZ

private subnet

  • ap-northeast-1a : /24 (256個)
  • ap-northeast-1c : /24 (256個)
  • ap-northeast-1d : /24 (256個)

public subnet

  • ap-northeast-1a : /26 (64個)
  • ap-northeast-1c : /26 (64個)
  • ap-northeast-1d : /26 (64個)

今回のようなVPCのサイズを変更したい場合、方法は2つあります。

aws.amazon.com

・追加の IPv4 CIDR ブロックをセカンダリ CIDR として VPC に追加する。

・希望する CIDR ブロックで新しい VPC を作成し、古い VPC から新しい VPC にリソースを移行する (該当する場合)。

今回は既にDBがVPC内で本番稼働していましたが、複雑なネットワーク構成になることを避けて、移行する方針を採用しました。

本番稼働していたDBは、DMS(Database Migration Service)を使って新しいVPCに移行しました。 オンプレからの移行の際にもDMSを使ったので慣れていましたが、DB移行自体が慎重に行う作業のため心理的に大変でした。(泣)

終わりに

VPCサイジングの失敗談を紹介しました。 今回のケースのように、ALBを複数立てる可能性があるサブネットを構成する場合、 /28 の割り当ては要注意です。

VPCは簡単に変更ができるものではありません。 必要なIPの見積もりは、サービスがスケールすることもありますので、多めに見積もることを推奨します。 また、各リソースのIP利用に関する仕様について把握することも重要だと言えます。

このブログがお役に立てれば幸いです。

参考記事

iga-ninja.hatenablog.com

dev.classmethod.jp

【外部勉強会】オンラインで開催するためにやったこと

はじめに

エキサイト株式会社 バックエンドエンジニアの山縣です。 先日、技術者が主催の外部勉強会を開催しました。

excite.connpass.com

勉強会のテーマは「リビルドへの道」。Zoomのウェビナー機能を利用してオンラインで開催をしており、 登壇者にはリビルドに挑戦していることやリビルドを完遂させたことについて発表していただきました。

本記事では外部勉強会をオンラインで開催するためにやったことについて記述しています。

勉強会開催ページの用意

前回開催した勉強会と同様に、connpassを利用することにしました。 IT業界の勉強会開催ページには、connpassが使用されることが多いかなと思います。 connpassでは参加者のみに視聴URLを送ることができたり、発表後に資料のリンクを掲載できたりと便利な機能が多くとても使いやすかったです。

サムネイル画像

SNSでconnpassのページを共有したときに、つい読みたくなってしまうようなサムネイル画像をデザイナーに用意してもらいました。 connpassでイメージ画像を設定するとOGP画像として登録されます。 実際にTwitterでリンクを共有すると下記図のように表示されます。

イベント説明

イベント説明欄にはどのような勉強会が行われるかについてまとめました。 他社事例や前回の開催内容を参考に下記内容を記載しました。

  • 概要
  • タイムテーブル
  • 登壇者と発表内容
  • 配信URLについて

connpassのイベント説明欄

ZoomのウェビナーURLの用意

社内で活用事例のあるZoomのウェビナー機能を利用することにしました。 勉強会参加者マイクやカメラをオフにする必要がないため、事故が少ないのがよいところです。 登壇者は自身のPCで参加して発表しています。

ウェビナーURLの配信

connpassではイベント参加者にメールを送る方法と、イベント参加者のみが見ることができるフィールドの2つが用意されています。 今回はその両方を活用して参加者に視聴URLを送ることにしました。

メール配信は、イベント管理 > メッセージ > 新規メッセージより送ることができます。

実際に配信したメール

ウェビナーの事前チェック

当日に事故なく開催するために、事前チェックはしっかり行っておくとよさそうだと考えてリハーサルを行いました。 今回は登壇者が自分のPCの画面を共有するようにしたため、各登壇者に簡単な発表練習をしてもらいました。 また、バーチャル背景を事前に設定しておくと当日切り替える必要がなくなります。

懇親会の用意

登壇者3名による発表終了後はバーチャル会議ツール「Remo」を利用してオンライン懇親会を行いました。 現在のRemoのFreeプランでは上限50名の45分利用可能であり、今回の参加人数内で収まるため採用しました。 Remoの設定も非常に簡単だったので、今後も利用していければいいなと考えています。

参加人数が多い場合はプランを上げたり、他のサービスを活用してもよさそうです。

Remoの設定画面

司会進行のスライドの用意

Zoomでのコメントや質問方法、雑談会場への導線、アンケートのQRコード表示などをまとめています。 スライドはCanvaを利用して作成しました。

当日の様子

当日の発表内容や開催レポートについてはチームメンバーが記事にまとめています。 下記リンクよりご確認いただけます。

tech.excite.co.jp

tech.excite.co.jp

www.wantedly.com

おわりに

本記事では、外部勉強会をオンラインで開催するためにやったことについてまとめました。

外部勉強会で登壇していただいた3名と運営メンバー、サムネイル画像を作成してもらったデザイナーなど、 多くの方の協力によって無事開催することができました。本当にありがとうございました。

今後も外部勉強会を開催したいと考えています。 最後まで読んでいただきありがとうございました。

Flutter2.10.0でAndroidアプリが次々とクラッシュしてしまうお話

エキサイト株式会社でモバイルアプリ開発に携わっている奥田です。 とあるAndroidアプリのFlutterへのリプレイス作業が終了し、リリースした際にクラッシュ報告が相次ぎました。今回はクラッシュの原因、対応した方法について記事にしていきます。

問題点

弊社ではFirebaseのCrashlyticsを使用しています。 リリース後に各画面で下記のようなエラー内容がコンソール上で出力され、クラッシュが増加しました。

Fatal Exception: java.lang.NullPointerException
Attempt to invoke virtual method 'void android.view.View.dispatchWindowVisibilityChanged(int)' on a null object reference

解決方法

公式のissueで解決方法が提示されました。 Flutterの2.10.0でバックグラウンドにアプリが移行するとクラッシュが発生することが確認できました。 Flutterのバージョンを2.10.3以上にあげることで解消するようです。

github.com

弊社でも2.10.0を採用していたので、この機会に3.0.3にアップデートしました。 その結果に上記のクラッシュが解消され、アプリの安定性が向上しました。 少しでも参考になれば幸いです!!

最後に

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期インターンも歓迎していますので、興味があれば連絡いただければと思います。今回の記事を読んで少しでも興味が湧きましたら是非応募お願い致します!!

就業型インターンの募集情報です! www.wantedly.com

募集職種一覧はこちらになります!カジュアル面談からでも構いません。 www.wantedly.com

CLI経由なら、ECSのALBは更新できる

こんにちは。 エキサイト株式会社の三浦です。

AWSにはECSという、コンテナをデプロイ・運用できるサービスがあります。

このECSにはALBを接続することもでき、それによってURLからのアクセスをECSのコンテナに流すことができます。

今回は、一度ECSにALBを接続するよう設定した後、その設定を更新する方法について説明します。

Amazon ECS

ECSはAWSのサービスで、コンテナをデプロイ・運用できるサービスです。

公式には以下のように説明されています。

aws.amazon.com

Amazon ECS は、フルマネージドコンテナオーケストレーションサービスであり、コンテナ化されたアプリケーションを簡単にデプロイ、管理、およびスケーリングできます。

このECSにはALB(厳密にはターゲットグループ)を接続する事ができ、それによって特定のURLへのアクセスをコンテナに流すことが出来ます。

さて、ECSに対してALBの接続設定をした後、何かしらの事情で変更したいこともあると思います。

ですが、実はWebコンソール上で変更しようとすると、以下のような表示が出ます。

実はWebコンソール上ではALB設定を更新することは出来ず、もし変更したいのであればECS自体を作り直す必要があるのです。

ですが当然、わざわざECSを作り直すのは大きな労力が必要になります。

なにか方法はないのでしょうか?

CLI経由でALB設定を更新する

実は、WebコンソールではなくCLI経由なら、ECSを作り直すことなくALBを更新できます。

aws.amazon.com

やり方は簡単で、AWS CLIaws ecs update-service を使用するだけです。

aws ecs update-service \
  --cluster *** \
  --service *** \
  --load-balancers targetGroupArn=***,containerName=***,containerPort=*** \
    targetGroupArn=***,containerName=***,containerPort=***

ただし、AWS CLIのバージョンが古いとうまく行かない可能性があるので、十分にアップデートしてから実行することをおすすめします。

また、Terraformでも同様に、バージョンを十分に上げればALB変更時に delete/create ではなく update で更新してくれるようです。

最後に

ECSへのALB設定は、多くの場合作成初期以外には変更する機会は少ないと思いますが、何かしらインフラで大掛かりなことをしようとすると、すでに稼働中でも変更する可能性はあります。

そういったとき、Webコンソールだけ見るとECSの作り直しが必要そうに見えますが、CLI経由でも作り直しなしの更新だけで済むのであればだいぶ手間が減るはずです。

この記事が参考になったら幸いです。

ペアプログラミング(仮)をチームで実施しました!

エキサイト株式会社SaaS事業部の菅間(@sugamaan)です。 普段は経営管理ツールKUROTEN. の開発に携わっています。

今回は、SaaS事業部にてペアプロっぽいこと(以下、ペアプロ)を実施しました!

背景

SaaS事業部は計4つのプロダクトを開発しており、新しいメンバーを積極的に採用しています。 今回は、若手メンバーの実力向上のためにペアプロを実施しました。

概要

若手メンバーと経験豊富なメンバーがペアを組み、2チームで実施をしました。

若手メンバーはコーディングを担当するドライバ、経験豊富なメンバーはコーディングを支援するナビゲーターとして担当を分けました。

作業をするドライバはコーディングをしながら自分の考えていることを声に出して作業を進めました。 それに対して、気になった点をナビゲーターが質問をしたり、意見交換をしながら進めていきました。

実施場所

現在、弊社は週2出社なため出社日に合わせてオフラインで実施をしました。

このような場合はオフラインと相性が良いので、そこがリモートと出社のMIXの良い点だと感じました。

時間

大体2 ~ 4時間ほど実施しました。

個人の感想

個人的にはかなり良い時間となりました。

良かったと感じた点
  • 便利なショートカットに関して知ることができた。
  • 先輩のコーディングを行うときの思考プロセスを知ることができた。
  • 自分に足りていない見積もり能力や要件定義のコツを作業をしながら学ぶことができた。
  • メンバーの作業環境のこだわりについて知ることができた(ブラウザの表示・タブの使い方など)
  • VuexやVuetifyに関してペアプロを通じて理解を深めることができた。
  • なぜこのような設計・コーディングをしているのか・コーディングの方針に関して理解を深めることができた。
  • タスクで詰まっているところを解消して進捗が出せた。(でかい)
  • 雑談を交えつつだったので楽しかった。

ざっとあげましたが良い点がたくさん出ました。

企画してくださったリーダー陣の皆さんに感謝です...!

発見・改善点
  • ナビゲーターは一方的に教える形になったので、メンバーを入れ替えて違う学びを得れるように試す。
  • オフラインだけではなく、オンラインで実施できないか検討してみる。
  • 実装だけではなくレビューや仕様検討でも面白そう。

やり方を工夫することによって、様々な学びが得れそうだと感じました。

チームの感想

今回参加したメンバーの感想

ナビゲーター

長谷川さん

相手の思考やちょっとした工夫をを共有することができ、個々の思考力のアップと、開発効率の向上に繋げられそうな会だったと思います。

鈴木さん

コードレビューよりも仕様を深堀りして聞くことができて理解につながりました。また、お互い知らなかったエディタの便利な機能を共有できたり、より密なコミュニケーションが取れました。

ドライバ

荒井さん

ナビゲーターが大先輩であったということもあり、思考スピードの違いをもろに感じました。IDEを用いたちょっとした工夫など今後役に立ちそうな知識も得ることができたのでとても有意義な会でした。

最後に

今後、SaaS事業部はメンバーが増えていくので、オンボーディングの中にペアプロを導入するのはありだと感じました。 今回の取り組みがチームにプラスになったと証明できるように業務でもアウトプットを出していきたいと思います。

最後にエキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを積極的に募集しております。 長期インターンも歓迎していますので、興味があれば連絡いただければと思います。

SaaS事業部のに関して

tech.excite.co.jp

菅間とのカジュアル面談はこちら

meety.net

募集職種一覧はこちらになります!(カジュアルからもOKです)

www.wantedly.com