iChain’s Engineer Blog

iChain株式会社の開発チームによるブログです

関数型っていいよね②

こんにちは。アプリ開発担当です。 以前手続き型と関数型の違いについて実際にコードを書きました。社内のエンジニアから続編を要望されたので、テーマを変えてまた書いてみたいとおもいます。今回のテーマはFizzBuzzです。

Fizz Buzz - Wikipedia

今回はJava11、標準ライブラリで書きました。

  void fizzbuzz1() {
    println("* fizzbuzz1()");

    for (var i = 1; i <= 100; i++) {
      if (i % 15 == 0) {
        println("FizzBuzz");
      } else if (i % 3 == 0) {
        println("Fizz");
      } else if (i % 5 == 0) {
        println("Buzz");
      } else {
        println(i);
      }
    }
  }
  void fizzbuzz2() {
    println("* fizzbuzz2()");

    for (var i = 1; i <= 100; i++) {
      var s = "";
      if (i % 3 == 0) {
        s += "Fizz";
      }
      if (i % 5 == 0) {
        s += "Buzz";
      }
      if (s.isEmpty()) {
        s += i;
      }
      println(s);
    }
  }
  void fizzbuzz3() {
    println("* fizzbuzz3()");

    var d = Map.of(3, "Fizz", 5, "Buzz", 15, "FizzBuzz");

    for (var i = 1; i <= 100; i++) {
      var b1 = BigInteger.valueOf(i);
      var b2 = BigInteger.valueOf(15);
      var gcd = b1.gcd(b2);
      println(d.getOrDefault(gcd.intValue(), Integer.toString(i)));
    }
  }

関数型だと、例えばこのように。

  boolean isFizz(int n) {
    return n % 3 == 0;
  }

  boolean isBuzz(int n) {
    return n % 5 == 0;
  }

  boolean isFizzBuzz(int n) {
    return n % 15 == 0;
  }

  String asFizzBuzz(int n) {
    return isFizzBuzz(n) ? "FizzBuzz"
        : isFizz(n) ? "Fizz"
        : isBuzz(n) ? "Buzz"
        : Integer.toString(n);
  }  

  void fp_fizzbuzz1() {
    println("* fp_fizzbuzz1()");

    IntStream.rangeClosed(1, 100)
        .mapToObj(this::asFizzBuzz)
        .forEach(this::println);
  }
  int increment(int n) {
    return n + 1;
  }

  void fp_fizzbuzz2() {
    println("* fp_fizzbuzz2()");

    var res = Stream.iterate(1, this::increment)
        .map(this::asFizzBuzz)
        .limit(100)
        .collect(Collectors.joining(System.lineSeparator()));

    println(res);
  }

  String combineLines(String a, String b) {
    return a + System.lineSeparator() + b;
  }
  void fp_fizzbuzz3() {
    println("* fp_fizzbuzz3()");

    Stream.iterate(1, this::increment)
        .map(this::asFizzBuzz)
        .limit(100)
        .reduce(this::combineLines)
        .ifPresent(this::println);
  }

いかがでしょう。 考え方、捉え方によって書き方は一通りではない、ということですね。簡単な動作の方が手軽に様々な書き方を試せるとおもいます。まだ関数型に慣れてない人は、このような簡単なものから始めた方がいいかもしれません。

関数型のほうが汎用性が高くなったり、複雑な処理も簡潔にかけるので便利なことが多いです。ただ処理スピードは手続き型の方が速いため、やむを得ず手続き型を使う場面もあります。また、実際の開発の場面では、関数型だけで実装できることは少なく、手続き型と併用して使用するケースがほとんどです。
重要なのは、要件や開発環境に合わせて適切に使えるということですね。

ただ、私は関数型が大好きです。

合わせて高階関数についても作成したので、そちらはいつか記事にしたいとおもいます。

※ iChainでは一緒に働く仲間を募集しています。よかったらHPとかwantedlyとかみて、是非応募ください!

AWS Well Architectrdレビュー

こんにちは。インフラ担当です。 先日AWS Well Architectrdレビューをうけることができました。せっかくなので内容や感想を紹介したいと思います。

AWS Well Architectrdレビューとは

こちらの公式ページではこのように書かれています。

AWS Well-Architected レビュー (ISV)

AWS Well-Architected レビューは、独立系ソフトウェアベンダー (ISV) が AWS のベストプラクティスを採用して、コストを削減し、セキュリティとパフォーマンスを向上させ、クラウドネイティブアーキテクチャを採用し、業界のコンプライアンスを推進し、トラフィックの需要を満たすようにスケールするのに役立ちます。

重要なのは「AWSのベストプラクティス」をもとに、自社のAWSのアーキテクチャをレビューしてくれるということです。このベストプラクティスは「AWS Well-Architected フレームワーク」とよばれ、AWSの公式ページで公開されています。

AWS Well-Architected フレームワーク

この資料自体とても勉強になるので、どんどん広がっていけばいいなと思います(なかなかのボリュームですが)。
ただ設計に関する原則(考え方)が記載されているだけで具体的な実装方法やアーキテクチャパターンについて触れているわけではないので、AWSで構築したアーキテクチャがある程度の規模になったら受けるのがいいのかもしれません。

内容

結果と詳細については機密事項も含まれるので紹介はできませんが、50程度のヒヤリング項目を事前に用意していただき、それをベースにiChainの今の状況についてフィードバックをもらいました。各項目の内容とともに、どのようになっていることがなぜ重要なのか、を説明してくれるので、現状とベストプラクティスとのギャップがわかりやすく(実現しやすいわけではない。。。)、新しい発見や初めての見方もあって面白かったです。 弊社の場合、セキュリティ周りでどうすべきか悩んでいた事項がヒヤリング項目に含まれており、その対応方針について相談することができたのがとても助かりました。

今回は10個以上の指摘事項があったのですが普通らしいです。あくまでベストプラクティスとのギャップなので、改善するのかどうかも判断で良いとのこと。ありがたいなとおもったのは、各項目について改善するかどうかの判断を我々に委ねつつ、改善する項目については優先順位を協議して、その後のロードマップを一緒に検討していただきました。その中でAWSのサービス活用の提案も受けながら進んでいきそうです。(彼らはここがポイントなのかもしれませんが。笑)

ちなみに、おほめいただいた点もいくつかありました。ネットワークの保護や転送データ保護など、セキュリティ面ではよく対応できているとのことでした。これは、iChainではIaCをすすめており、terraformとAWSの連携を進めています。また、機能をモジュール化して可搬性を高めているので、全てのプロダクト/PJで適用できる体制にしているため、セキュリティレベルの高い開発が出来ています。

実際うけてみて

今回のレビューは2回4時間にわたってうけました。(めちゃ疲れました。。)
AWSの担当者の方はフランクで親切な方で、和やかな雰囲気で進みました。また、iChainの場合、サービスの提供先が保険会社等の金融機関なので、FISC安全対策基準にも対応していく必要があるのですが、そういった部分でのサポートを提案していただき、とてもありがたかったです。

金融機関は金融庁による免許事業でセキュリティ基準がとても厳しく、クラウドも10年ほど前は考えられない世界だったという話も聞きました。それがいまではAWSをはじめ、クラウドサービスの導入が進みつつあるということで、大きな時代の変化を感じます。我々もサービスの基盤を整え、大きく展開していきたいと思います!

※ iChainでは一緒に働く仲間を募集しています。よかったらHPとかwantedlyとかみて、是非応募ください!

イベント駆動型アーキテクチャ / DynamoDBの実例

iChainの開発全般を担当しています。初記事になります。よろしくお願いします! 今回はDynamoDBをつかった、サーバレスアプリケーションの事例について書きたいと思います。こんな案件の時に、というシナリオをベースにしながら紹介していきますね。

開発の要件

以下の要件がベースになります。

  • 一般消費者からの注文を受けるサイトを構築する
  • 注文情報と注文の通知は、1日1回に集約して行う
  • 注文者に対しては、注文完了時にメールを送信する

DynamoDBとは

Amazonのサービスの一つです。公式の紹介はこちら。

Amazon DynamoDB(マネージド NoSQL データベース)| AWS

DynamoDBの特徴は下記の通りです。

  • フルマネージドサービス
  • NoSQLデータベース
  • サーバーレス、データ拡張性
  • 数ミリ秒台のレスポンスタイム

NoSQLデータベースに関してはAWSのホームページ(https://aws.amazon.com/jp/nosql/)に詳しく記載されているのでそこを参照すると概要が把握できると思います。 特に気に入っているのが1つのテーブルに1つのデータモデル(Schema)を定義する必要がない点で、実装しながら適宜柔軟にSchema変更していくことができます。これによってあれこれ試しながら開発をすすめることができるのでアジャイル開発にも向いているかもしれませんね。

ちなみに、DynamoDBはキーバリュー型です。キーと値によるシンプルな方法を使用してデータを格納しており、応答速度が早いのが特徴です。

上記のような特徴から、手軽にwebアプリケーションを構築したい、かつ顧客情報をうけとりサービスとして活用したいというケースでよく使われます。身近な例としては、web広告でのマーケティングデータ管理、ゲームやエンタメでのコンテンツ管理と顧客データ管理、ECサイト等での顧客データ管理です。

開発の内容

要件を踏まえ、イベント駆動型アーキテクチャで構成すること、DynamoDBを使用することとしましょう。

イベント駆動アーキテクチャ

独立した機能同士をイベントを通じてつなげることで、各機能間の結合度を下げ、各機能は自分の仕事に注力できるようになります。 今回の要件に「注文を保存し、注文完了メールを送信する」というものがありますが、これを - 注文を保存する機能 - メールを送信する機能 のように独立した機能として分離させ、この2つを「注文が保存された」というイベントを通じてつなげます。 具体的には

  1. 「注文を保存する機能」が注文データを保存する
  2. 「注文が保存された」というイベントが発行される
  3. 発行されたイベントに「メールを送信する機能」がキャッチし、イベント内容に基づいてメールを送信する

ということになります。これだけだと動くことはわかるけど何がうれしいのかわかりませんね。

メリットを享受できるのは機能の追加等改修が発生した場合です。
例えば保存された注文データを基に何らかの処理が必要になった場合、「注文が保存された」というイベントをトリガーに動くように独立した新機能を追加すれば既存の「注文を保存する機能」や「メールを送信する機能」には全く手を加える必要がありません。他の処理への影響を考えることなく新機能を追加できるようになります。

機能を変更したり、削除する場合も同様です。各機能が独立して他の機能に影響を与えないので改修がかなり楽になります。ではどうやってこの「注文が保存された」というイベントを発行するのでしょうか?今回はDBへのデータ保存をトリガーにイベントが発行されると都合が良いです。そんなときに使えるのがDynamoDB Streamです。

DynamoDB Stream

DynamoDBに付随する機能で、データ登録、更新、削除等データへの更新処理が発生したことを検出し、別サービスに通知することができます。これによりイベント駆動のアプリケーションを構成することが可能になります。使いたかった「注文が保存された」というイベントを実装できるわけです。

今回独立させた2つの機能「注文を保存する機能」と「メールを送信する機能」はそれぞれAWS Lambda で実装しています。最終的には以下のような形になりました。

  1. 「注文を保存する機能(Lambda)」がDynamoDBにデータを保存
  2. DynamoDBが DynamoDB Streamを使ってイベントを発行
  3. イベントを受け取った「メールを送信する機能(Lambda)」がメールを送信

Time to Live

DynamoDBに付随する機能で、データに有効期限を設定し、その期限を切れたものはDynamoDB側が削除してくれます。

顧客に申込データを通知したら、もうそのデータはそれ以降使用しない仕様となったため、ゴミとしてデータが残ります。ただ、このデータも補完すればストレージを使用するので課金対象となってしまいます。しかし、ゴミデータ削除バッチのようなものをこのために作るのも手間です。そんなときにこの機能を使うことで、運用コストを最適化できます!!!普通に便利です。

おわりに

ざっと概説しました。iChainではマイクロサービスアーキテクチャが大ブームなので、一つの事例としてDynamoDBを紹介してみました。
DynamoDBはちょっとしたDBをつくるときに便利なので(それ以外でも便利なんですが)、いろいろ触ってもらったらいいんじゃないかな〜と思っています。

※ iChainでは一緒に働く仲間を募集しています。よかったらHPとかwantedlyとかみて、是非応募ください!

Kubernetesの概要

はじめまして。インフラエンジニアとして、業務システムの環境構築を担当しております。まだwebエンジニアからインフラエンジニアへ転身してからは3ヶ月程度で、日々勉強量が多くて大変です。完全クラウド環境かつAWSのサービスを数多くつかっているので、その各サービスでできることとiChainの環境でやっていることを学ぶのが大変です。また、ネットワーク関連の知識も必要なので、この辺りは苦戦しています。。。修行中の身ですが、頑張っていきたいと思います。

今回はKubernetesについてお話ししたいと思います。今回はざっくりした概要を説明できればと思います。
ちなみに、弊社でAWSをベースにして環境を構築していることは先の記事の通りで、実際に使っているサービスはAmazon EKSです。

コンテナ運用・管理ツール

KubernetesはもともとはGoogle社内の運用システムBorgが出発点で、2018年頃にかけてAWS、Azure、IBMなどで本格的なサービスが開始し、コンテナオーケストレーションエンジンのなかでは77%のシェアを占めています。
コンテナオーケストレーションツールとは、コンテナ化されたアプリケーションの運用、管理、監視、デプロイ等の自動化をする為のオープンソースソフトウェアになります。コンテナの詳細は省略しますが、弊社ではDockerを使用しています。

コンテナのメリットは一言で言えば、環境構築を早く簡単にする、ということです。必要な資材を一つのパッケージにして別環境に適用することができるためですが、これは同時に運用保守を軽量化することにもつながります。 iChainではマイクロサービス化を進めていることは先の記事にありますが、少人数で2つの自社サービスを開発・提供するためには開発効率・運用効率を極限まで高める必要があります。また、特に業務システムであるiChainBaseは開発規模が大きいため、コンテナ管理も最適化する必要があり、Kubernetesを導入しました。やはりサービスの立ち上げ段階からKubernetesに精通していたエンジニアがいたことは幸運だったと思いますし、そのような環境で仕事ができることは恵まれていると感じています。

Kubernetesのメリット

Kubernetesにより、下記が実現できます。

  • 複数コンテナの共存
    :1つの管理単位に複数のコンテナを組み合わせることでき、複雑な機能を持つアプリケーションを簡単に構築できます。
  • オートスケール
    :負荷に合わせてコンテナを自動的に増減できるので、サーバーダウンを回避するとともに、ランニングコストを低減できます。
  • セルフヒーリング :障害発生時に自動的に復旧を試みる機能により、ダウンタイムを最小化できます。
  • ローリングアップデート :ダウンタイムなしでデプロイを実行できます。

SaaSサービスをやる上で、ダウンタイムを極小化することでは重要な要素です。また、サービスの立ち上がり段階では開発とアップデートが頻繁にあるでしょうし、ユーザー数の増加が予測できないのでサーバリソースの管理とコスト管理を手動で行うのは困難でしょう。逆にいうと、この辺りの必要性はサービス特性に依存する部分なので、必ずしも必須ではないこともあるでしょうね。

パイプライン構成

iChainではargoCDを利用してパイプラインを構築しています。構図としてはGitLab-argoCD-Kubernetesになります。
argoCDはGitOpsを実現するツールですが、要はgitを起点としてコードの変更を、アプリケーションとインフラの両方への適用(デリバリー)を自動化するものです。コードの変更は日に何度も行うので、デリバリーを自動化することで、単純に開発スピードの向上、省力化、ミスの削減が期待できます。業務の自動化はiChainでは徹底するようにいわれているので、極力マンパワーが介在しない業務環境の構築を心がけています。
CI/CDも導入していますが、これは既存サービスを使わず、GitLabの機能を利用しながら自社で構築しています。構築したこともですが、自分たちで作ろうと決めたことがすごいですよね。。

インフラではGitLab-Terraform-AWSのパイプラインもあり、開発スピードをあげています。このあたりの構成や構築についてはまた別の機会に触れていければと思います。

※ iChainでは一緒に働く仲間を募集しています。よかったらHPとかwantedlyとかみて、是非応募ください!

アプリのプッシュ通知の実装について

アプリのフロントの開発を担当しています。

iChainではアプリをflutterで開発してます。サーバーサイドで一部、Firebaseを使用していますが、flutterでFirebaseを使う方法について、調べてもあまり出てきません。きっと同じ悩みを持っている方もいると思うので、今回はプッシュ通知機能の実装についてまとめてみました。 プロダクトの詳細を書けないので、やや抽象的な内容になってしまってますが、なにかの参考になれば嬉しいです。

「プッシュ通知の実装」

※ iChainでは一緒に働く仲間を募集しています。よかったらHPとかwantedlyとかみて、是非応募ください!

関数型っていいよね、って思います。

アプリ開発を担当しています。どんなネタがいいかな〜と悩みましたが、手続き型と関数型の違いを説明しようかなと思いました。

みなさん最初は手続き型から入るとおもうのですが(よく見る教材は全部手続き型ですよね)、最近の開発のトレンド(マイクロサービス化とかね)からすると、関数型の方がコードを小さいユニットに分解できて、良い選択になってきていると思いますし、iChainのサービスもそのトレンドを踏襲しています。また、シンプルかつコードの安全性を高めやすいので、個人的には関数型を強く推してます。

ただ、「こっちの方がいいよ!!」って叫んでも伝わらないですよね。それなら実物を見てみましょう!というわけで。みんな大好きwcコマンドを手続き型と関数型で書いてみました。 (Java8、標準ライブラリだけで書きました。)

まず手続き型。

package jp.co.ichain.study.javaproc;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;

public class App {
  public static void main(String[] args) {
    int tlCtLines = 0;
    int tlCtWords = 0;
    int tlCtBytes = 0;
    int ctFiles = 0;

    for (String path : args) {
      File file = new File("..\\" + path);
      if (!file.isFile()) {
        continue;
      }

      int ctLines = 0;
      int ctWords = 0;
      int ctBytes = 0;

      try (Reader reader = new BufferedReader(new FileReader(file))) {
        boolean inWord = false;
        int ch;
        while ((ch = reader.read()) != -1) {
          ctBytes++;

          if (Character.isWhitespace(ch)) {
            if (inWord) {
              inWord = false;
              ctWords++;
            }

            if (((char) ch) == '\n') {
              ctLines++;
            }
          } else {
            inWord = true;
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }

      tlCtLines += ctLines;
      tlCtWords += ctWords;
      tlCtBytes += ctBytes;
      ctFiles += 1;

      System.out.printf("l: %d, w: %d, b: %d, f: %s\n", ctLines, ctWords, ctBytes, path);
    }

    if (ctFiles > 1) {
      System.out.printf("l: %d, w: %d, b: %d, f: total\n", tlCtLines, tlCtWords, tlCtBytes);
    }
  }
}

どうですか?

続いて関数型。

package jp.co.ichain.study.javafunc;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.collectingAndThen;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;

public class App {
  private static class CountResults {
    private final String path;
    private final int ctLines;
    private final int ctWords;
    private final int ctBytes;

    CountResults(final String path, final int cls, final int cws, final int cbs) {
      this.path = path;
      this.ctLines = cls;
      this.ctWords = cws;
      this.ctBytes = cbs;
    }
  }

  private static byte[] readAllBytes(final Path p) {
    try {
      return Files.readAllBytes(p);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  private static IntStream newByteStream(final byte[] bs) {
    final ByteArrayInputStream input = new ByteArrayInputStream(bs);
    return IntStream.generate(input::read).limit(input.available());
  }

  private static boolean isLF(final int c) {
    return ((char) c) == '\n';
  }

  private static int countLines(final byte[] bs) {
    return (int) newByteStream(bs).filter(App::isLF).count();
  }

  private static int countWords(final byte[] bs) {
    return new String(bs).trim().split("\\s+").length;
  }

  private static CountResults countFile(final File f) {
    final byte[] bs = readAllBytes(f.toPath());
    return new CountResults(f.getPath(), (int) countLines(bs), countWords(bs), bs.length);
  }

  private static void printCountLine(final CountResults res) {
    System.out.printf("l: %d, w: %d, b: %d, f: %s\n", res.ctLines, res.ctWords, res.ctBytes, res.path);
  }

  private static File newFile(final String path) {
    return new File("..\\" + path);
  }

  private static CountResults sumCountResults(final CountResults a, final CountResults b) {
    return new CountResults("total", a.ctLines + b.ctLines, a.ctWords + b.ctWords, a.ctBytes + b.ctBytes);
  }

  public static void main(final String[] args) {
    final List<CountResults> resList = Arrays.stream(args)
        .map(App::newFile)
        .filter(File::isFile)
        .map(App::countFile)
        .collect(collectingAndThen(toList(), Collections::unmodifiableList));

    resList.forEach(App::printCountLine);

    if (resList.size() > 1) {
      resList.stream()
          .reduce(App::sumCountResults)
          .ifPresent(App::printCountLine);
    }
  }
}

オプションがないとか言わないで。(意外と大変だった)

どっちがいいか、ケースバイケースなことが多いので、目的や会社の方針に合わせて選択するのでしょう。

でも私は、関数型が好き。

※ iChainでは一緒に働く仲間を募集しています。よかったらHPとかwantedlyとかみて、是非応募ください!

iChainのインフラ構成の考え方 〜Microservice Architecture〜

こんにちは。iChain インフラ担当です。 前職ではExcel設計書・パラメータシートベースでの構築が殆どだったのですが、iChainではAWSインフラの構成管理にTerraformを採用しています。

背景

弊社でのサービスプロダクト開発は技術方針としてMicroservice Architectureを採用する方向で固まっており、これらを実現する為のDocker/Kubernetesの採用となっています。 なお、弊社ではクラウドプロバイダのAWS主に利用しており、アプリケーションサービスプロダクトのインフラにKubernetes(EKS)を採用しています。サービスプロダクト毎にEKSクラスターを運用している形となり、上記の環境上に各種ProjectのMicroserviceリソースをデプロイしています。 このような複数のクラスターを運用していく上で、iChainとしての統一的なAWSインフラリソースアセットの構築・管理を行うのに手動での対応には限界があります。その為のIaC(Infrastructure as Code)化は必須であり、そのための構成管理ツールとして、Terraformを採用しています。

Terraformのメリット・デメリット

構成の可視化

メリットとしては、構成がcode上に記載される為、現状のインフラ構成がどうなっているかを正確に把握する事ができます。弊社ではバージョン管理にGitlabを採用しており、Terraformのcodeはそれぞれのプロジェクトのインフラ用レポジトリに格納されています。

再利用性

Terraformでは複数のリソースをmoduleとして定義し、異なる実行環境からmoduleを呼び出す事が可能です。この機能を利用し、異なる環境(e.g. Staging,Production)上に冪等性の高いインフラリソースを構築する事ができるようになります。

デメリット

デメリットは構成のdriftが起こってしまう為、手動での対応が原則利用できなくなるという点です。ここはIaCを採用する上でのトレードオフとなる点ですが、メリットの方が上回ると判断しています。あとは、TerraformのCodingに独自言語HCLを利用する形ですが、こちらに学習コストを一定量かける必要がある点があります。

個人からチームへ

私がjoinした当時はインフラ担当が一人という事で、必然的にTerraformのCode maintananceを行うのも私のみという状況でした。 その為、ローカル上でのplan/applyとなる為CI/CDは必要なかった形ですが、サービスプロダクトのグロースに伴い(有難い事です)、インフラもチーム体制を構築する形となり、Terraformを利用したリソースデプロイに関してもCI/CD環境が必須となってきます。

TerraformCloudでMerge RequestベースのCI/CD環境構築

チーム開発体制に移行するにあたり、CI/CD環境を構築する事は必須でした。 アプローチとしては以下がありました。

  • リモートソースコードレポジトリのCI/CD pipeline上にスクリプトを構築する(Gitlab CI等)
  • レポジトリから独立したCI/CD pipelineを構築する(jenkins, CircleCI, Codebuild/Codedeploy等)
  • terraform cloudをリモートソースコードレポジトリと連携する 上記3点、それぞれメリット、デメリットがあるのですが、最終的にterraform cloudを採択する事に至りました。理由は以下です。
  • メリット
    • 承認フロー付のdeploy pipelineが低工数で構築できる事
    • リソースをterraformで管理できる事
  • デメリット pipelineのカスタマイズ性が低い事 それぞれメリット、デメリットがあります。弊社がスタートアップである為、いずれの方式を取るにせよ、工数には限りがあります。 限られた資源で仕組みを構築する為に、terraform cloudが最適であると判断し、導入に至っています。 現在は以下の方式で開発フローを回しています。
  • Gitlab上にてトランクからfeatureブランチを作成
  • Merge Request作成
  • tfcloudがソースコードの差分を検知
  • ソースコードレポジトリと連携しているterraform cloud workspace上にてterraform planが作成
  • merge時、ソースコードレポジトリと連携しているterraform cloud workspace上にてterraform applyが作成
  • 上記confirm時、各環境宛てのapply(デプロイ)が実行 Terraform Cloudを利用したCI/CDデプロイパイプラインを構築する事で、 plan, apply時にレビュアーによるレビュー、承認を取る運用体制となり、 開発リソースに対し一定の品質を担保できるようになりました。

    Terraform Cloudを Terraform Cloudで管理する

    通常、Terraformを利用する際はAWS, Azure, GCP等のクラウドインフラを管理するイメージが強い方も多いかもしれませんが、 その他にも色々なリソースを管理する事ができます。 terraform cloudのworkspace設定はwebGUIから設定する事もできるですが、この設定自体もterraform providerとして管理する事が可能です。 https://registry.terraform.io/providers/hashicorp/tfe/latest 実際のコードサンプルを下記に記載します。

resource "tfe_workspace" "default" {
  for_each              = local.tf_workspace_each
  agent_pool_id         = ""
  name                  = each.value.workspace_name
  organization          = tfe_organization.ichaininc.id
  terraform_version     = each.value.terraform_version
  working_directory     = each.value.working_directory
  file_triggers_enabled = each.value.file_triggers_enabled
  execution_mode        = each.value.execution_mode
  ssh_key_id            = ""
  trigger_prefixes      = each.value.trigger_prefixes_list
  vcs_repo {
    identifier         = each.value.vcs_repo_id
    ingress_submodules = each.value.vcs_ingress_submodules
    oauth_token_id     = var.gitlab_oauth_token
    branch             = each.value.vcs_branch
  }
}
resource "tfe_notification_configuration" "default" {
  for_each         = local.tf_workspace_each
  name             = "${each.value.workspace_name}-notify"
  enabled          = true
  destination_type = "slack"
  triggers = [
    "run:created",
    "run:errored",
    "run:needs_attention",
    "run:completed"
  ]
  url          = var.slack_webhook_url
  workspace_id = tfe_workspace.default[each.key].id
}

実際の運用では複数のworkspaceを立てる事になる為、予めfor_eachにて複数のリソースを構築する形でcodingしています。 新しくworkspaceを追加する度にfor_eachにて読み込むパラメータを更新する形となります。 その他、ここには記載していませんが、AWS Credentialの設定等もcode化できます。 詳しくは公式documentをご確認ください。

課題等

こうしてCI/CD周り環境を整えて運用する事はできていますが、現状まだvalidationチェック、セキュリティポリシーテスト等が pipelineに載せる事が出来ていないので、ここは随時改善できればと考えています。

最後に

iChainでは保険業界向けのSaaSプロダクトを担うAWSインフラの改善、向上に努めてくれるエンジニアを募集中です。 今回取り上げたCI/CD pipelineの構築・運用等、まだまだ改善すべき点は多々あるので、一緒にプロダクトを成長させていく事に興味がある方、 是非応募ください!よろしくお願いいたします。