クラスメソッドメンバーズをやめていいんじゃないかと検討して継続になった話

メリークリスマス!

id:shrkw です。今日で現職最終日だからというわけではないですが、最近、クラスメソッドメンバーズをやめていいんじゃないかと検討しているので、そのことを書きます。

サービス自体は AWSの請求代行から構築まで支援「クラスメソッドメンバーズ」|クラスメソッドのサービス の通りで、利用料割引とサポートが主な利点だと思います。

弊社はプレミアムサービスに加入しており、それは本当に助かっているのですが、いくつか未知だったデメリットがあったので記録しておきます。

デメリット 1: AWS Organizationsが利用できない

メンバーズにログインすると閲覧できるFAQに以下のように記載があります。

AWS Organizations を利用することはできますか?

弊社メンバーズサービスを利用いただいている AWS アカウントでは、AWS Organizations をご利用できません。

クラスメソッドメンバーズでの請求代行はクラスメソッド側のOrganizationsの配下に入ることで実現されるので、自由にOrganizationsを利用することはできません。正確には、絶対利用できないわけではなく、メンバーズ加入前に作成したOrganizationsはそのまま使えて、加入後にOrganizationsを作るのがダメなようです。ちょっと前に見たときは面倒なフォームから申請すれば作れたような気がするのですが、変わったのかな。

最近のAWS管理はOrganizationsを複数分けてACLにするというのが王道だと思うのですが、それができないというのが非常に痛いです。

デメリット 2: AWS SSOが使えない

AWS Organizationsが自由に使えないため、AWS SSOが利用できません。 FAQは下記。

AWS Single Sign-On (SSO) は利用できますか?

AWS Single Sign-On (以降、AWS SSO) を利用する際の前提条件として AWS Organizations のマスターアカウント管理権限が必要であるため、メンバーズへ加入されている AWS アカウントでは AWS SSO を利用することができません。(2018年 1月現在)

AWS SSOもセキュリティ管理に大きな利点のあるサービスなのでこれが使えないのというのもとても痛いです。

デメリット 3: 請求から支払いまでのサイトが30日しかない

弊社の場合、請求が毎月10日頃通知されてきます。これだけならいいんですが、支払い期限が翌月10日だったりして、支払いまでの期間が短く、経理部門に負担を掛ける運用になってしまっています。

サービス紹介サイトにはこう書いてあるけどなんで違うんだろ。

  1. 支払い方法、支払いサイクルを教えてください

銀行振込(円建て)でのお支払いとなります。為替レートは三菱UFJリサーチ&コンサルティングの月中平均を利用しています。お支払いの期日は月末締め翌月末支払いとさせていただいております。ご希望に応じてドル建てでのお支払いも可能です。

デメリット 4: Developers.ioで紹介された機能でも使えないと悲しい

上記のAWS SSOのように、クラスメソッドメンバーズであるせいで使えないものがDevelopers.ioで紹介されていたりすると、非常に悲しい気持ちになります。

デメリット 5: Organizationsが利用できないなどの情報がログインしないと閲覧できない

サービス加入時には説明があると思うんですが、デメリットになりうる情報こそ、パブリックに公開されている状態になっていてほしいなと思います。

社内で話して継続を決めた

などなど色々厳しい点があるのですが、サポート問い合わせのやりやすさ、脆弱性診断などが有償オプションとして可能という点が、人的リソースが常に足りないスタートアップとしては大きなメリットであり、加入継続となりました。上記のOrgの課題はAWSではどうしようもないところなのでしょうがないかなと思いつつ、情報公開などについてはもうちょっと改善してくれるといいなと思っております。

zencoder ことはじめ

こんにちわ、id:gaoohです。

動画の変換処理にzencoder を導入してそこそこよかったのでまとめます。

Viibarという会社はB2B動画にかかわるトータル業務を請け負っています。 サービスとしてはVyncというクラウド制作管理ツールを提供していますが、基本的にサービスとして動画に関わることが多いので、社内ツール含めて動画ファイルはよく扱います。

viibar.com

動画ファイルと言っても、一般的にYouTubeや各種プラットフォームで再生される動画は mp4 ( h.264 ) であることがほとんどです。 ただしそれ以外の媒体に入稿する場合もありますし、元データは必ずしも mp4 ( h.264 ) ではないこともあります。 なので、エンコード処理は切っても切り離せません。

かと言ってこれらの処理は時間も計算リソースも食うのでなるべくならクラウドサービスにまかせたい。 ffmpeg とかで頑張りたくない!

AWS上にシステム展開している場合は、現状だとAWS Elemental MediaConvert を使うのが一番妥当な選択肢だと思います。ただいろんな事情でVyncではElasticTranscoder を利用してはいますが。 とはいえElasticTranscoderでもよほど特殊な変換をしない限り事足りますし、スケールしたい場合の方法も用意されており、もろもろ安心感もあります。

以前はAWS上にシステムを構築することが多かったのでそれ以外の選択肢を考える必要もなく済んでいたのですが、最近は用途によってGCP上に構築することも多く、GCP上にはお手軽に試せるAWS Elemental MediaConvert相当なフルマネージドなサービスはないので(2019/12月現在)、「動画のエンコードどうしよう」という問題になりました。

もちろんシステムとしてはGCP上に構築して、ファイルはs3上に保存し、トランスコードはAWSでみたいな構成はできなくはないけれど、うーん、もうちょっと楽したい。

ということでいろいろ外部サービスを検討した結果 zencoderの導入をしました。

zencoder.com

GCS との連携

GCP上で連携したいGCSバケットの読み込みと書き込みを許可したサービスアカウントを作成し、Access keyを発行、それをzencoder に設定します。

Google Cloud Storage トリガー を利用して、アップロードされたファイルを変換する

zencoder は node 用のライブラリがあるのでこれを利用します

github.com

sampleコードは筆者がTypeScript推しなので TypeScript です。

import * as Zencoder from 'zencoder';


export const transcode = functions
  .region('asia-northeast1')
  .storage.object()
  .onFinalize(async object => {
      
  // zencode側のAPIキーとCredentialsのNicname、出力バケット名などはfunctions.config()で管理
  const config = functions.config();  
  const outputBucket = config.output.bucket;
  const credential = config.zencoder.credential;
 
   const params = {
      input: `gcs://${this.object.bucket}/${this.object.name}`,
      credentials: credential,
      region: 'asia-tokyo',
      outputs: {
        url: `gcs://${outputBucket}/${this.object.name}`,
        credentials: credential,
        format: 'mp4',
        headers: {
          'Content-Type': 'video/mp4'
        },
        quality: 4,
      }
    };

    const zencoder = Zencoder(config.zencoder.api_key);
    await this.zencoder.Job.create(params)
      .then(({ data }) => {
        logger.info(`zencoder request: ${JSON.stringify(data)}`);
      })
      .catch(err => {
        logger.error(`zencoder request error: ${JSON.stringify(err.errors)}`);
      });
  });

お手軽!

変換エラー時の処理

フルマネージドなAWS Elemental MediaConvert や ElasticTranscoder の場合、処理の開始と完了はSNS経由で通知を受け取ったり、SQSへ貯めるなどの対応可能です。

zencoderの場合は正常にエンコード処理が終了したら、指定した場所にファイルが作られるだけなので、エラー通知やJob管理が必要なら別途作り込む必要があります。APIでJobの進行状況や完了状況は問い合わせ可能なので、仕組み的には対応は可能です。

変換スピード

zencoderは公式でパフォーマンスは数百件の同時ジョブ送信のテストにおいて Amazon Elastic Transcoder の平均 14 倍といっています。 利用しているシステムが違うため、パフォーマスを正しく比較しているわけではないので、同時処理数が多い場合は比較検討の余地があるかと思います。

料金

zencoder側はプランによって違うので比較はしにくですが、一番安い月1000分までで1分あたり4.3円で、用意されている最上位プランまであげると1分あたり2.1円ぐらい。 対してAWS Elemental MediaConvert は出力フォーマットによってかなり値段が違いますが、だいたい1分あたり2.3円ぐらい。 システム全体でいうと、当然ファイルの転送量などを加味されるので、適用するシステムによる部分はありますが、単体の料金的にはどちらも遜色ない感じです

社内向けWebアプリのフロントをスプレッドシート+GASで作る

こんにちは。Webエンジニアの @umanoda です。 今年のブラックフライデーfitbit versa 2 を買いました。使い勝手よくて気に入っているのですが、手首と電極が触れているところがカブれてしまいました……。惜しい。

さて最近、社内向けのWebアプリをGASで作成しました。 弊社では G suite を利用して社内アカウントを管理しています。GASを利用することで、Webアプリで必要になるユーザー認証周りの処理をまるっと省略できるのが便利だと感じたので紹介したいと思います。

実現したいこと

  • G suiteに登録されている社内ユーザーにWebアプリでサービスを提供したい
  • 誰が実行者かを判別したい
  • 実行者にファイルをダウンロードさせたい

実現方法

共有ドライブに置いたスプレッドシートとGASをフロントとして利用しました。

見た目にはこだわらない、動けばいいやのショボアプリです。ざっくりと以下のような仕組みで動きます

f:id:umanoda:20191223190746p:plain

  • フォームっぽいものをセルで表現する
  • ボタンっぽいものを図形の挿入で表現する
  • ボタンにスクリプトを割り当て、クリックしたときにGASが実行されるようにする
  • GAS内部でセルの情報を読み取り、バックエンドのAPIにリクエストを投げる。バックエンドはGoogle Cloud Functionsを用意
  • ファイルをダウンロードするために、モーダルダイアログを開く

今回、スプレッドシート+GASをつかってファイルをダウンロードするアプリで、こういう工夫をしたというところを二点紹介します。 なお、GASの一般的な使い方の説明は今回しません。

GASでのユーザー認証

社内システムではあるのですが、特定の担当者にだけ利用させたいというニーズがあり、実行ユーザーの制限を必要がありました。 GASの Session.getActiveUser()) によって操作しているG suiteにログインしているEmailを取得することができるので、これを利用します。

実行できるユーザーを限定する場合、「シートの保護」機能をつかって一部のユーザーにしか変更できないシートを用意すると便利です

// GAS: Code.js
function main() {
  if (!_validateAccount()) {
    SpreadsheetApp.getUi().alert("実行権限がありません");
  }

  // 続きの処理。APIリクエスト組み立てなど
}

function _validateAccount() {
  var currentAccount = Session.getActiveUser();

  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName("管理者用");
  var range = sheet.getRange(1, 2, sheet.getLastRow(), 1); // 「管理者用」シートのB1から最後まで取得
  var rangeValues = range.getValues();

  for (var idx=0; idx < rangeValues.length; idx++) {
    let row = range_values[idx][0];
    if (row == currentAccount) {
      return true;
    }
    return false;
  }
}

注意点としては、GASはスプレッドシートの編集権限があれば変更することができます。実行ユーザーの権限チェックをコメントアウトするなどされると、このシートにアクセスできる人ならば誰でもチェックをすり抜けることができてしまいます。 厳密にやるなら、APIリクエストのサーバーでも権限チェックが必要でしょう。あくまでここでは、「余計なリクエストが走らないためのチェック」「誰が担当者として指名されているのかの明示化」程度のもの割り切りきっています。

ファイルのダウンロード

GASの処理はサーバー側で完結してしまうため、ユーザーにファイルをダウンロードさせるには一工夫が必要です。 テンプレートファイルを用意することで、レンダー結果がiframeに描画されたモーダルダイアログをスプレッドシート上に表示することが出来ます。

Ref. HtmlService | G Suite Developer

// GASでテンプレートを呼び出す

// hoge.html をテンプレートとして使う
var template = HtmlService.createTemplateFromFile("hoge");

// 変数に値やメソッドを設定
template.huga = "aiueo"
template.piyo = function () { alert("kakikukeko") }
テンプレート側での呼び出し
<div>
  <?= huga ?> // 値の埋め込み
  <? piyo() ?> // メソッドの実行
</div>

これを利用して、以下のようにするとGASで生成したURLからファイルをダウンロードすることが出来ます。

<!-- GAS: index.html -->
<html>
<body>
  <p>ダウンロード中です</p>
  <a id="download" href="<?= url ?>" target="_blank">ダウンロード</a>

<script>
  var a = document.getElementById("download");
  a.click();
</script>
</body>
</html>
// GAS: Code.js
var template = HtmlService.createTemplateFromFile("index");
template.url = downloadUrl;
SpreadsheetApp.getUi().showModalDialog(html.evaluate(), "Dialogタイトル");

コード管理

GAS上で使えるエディタだと、最低限の構文チェックのみしかされません。ESLintなどを活用するため、ローカルでコーディングして clasp を使ってデプロイするようにしています。

Typescriptで書いたコードをclaspでデプロイすると、自動的にトランスコンパイルしてサーバー上に配置してくれるのがとても便利です。

まとめ

社内向けのちょっとしたサービスを作る際のTipsとして、スプレッドシート+GASでのフロントエンド構築について書きました。

G suite管理下で公開範囲を限定したサービスを手軽に作れるのが最も大きな利点です。その他、スプレッドシートの入力規則を利用して手軽に入力フォームを作成できるのも良い点かなと思います。

欠点としては、スプレッドシート上が自由に編集できてしまうため、容易にアプリケーションを壊せてしまいます。防ぐためには「シートの保護」機能を使って編集できるセルを限定するなどの工夫が必要となります。

公開範囲をカスタマイズしたり、もっとリッチなGUIを提供するのならば、Firebaseを使うのも良いでしょうね。

MySQLとmacOSにおける大文字・小文字の扱いについて知ったこと

こんにちは。開発部でWebエンジニアをしているjwmxです。

最近、よく利用しているスーパーがセルフレジを大幅に増設し、5・6台あった従来のレジが2台になりました。
時代の流れを感じます。

さて、先日、ローカル開発環境(Mac)で通ったMySQLのSELECT文を別の環境(GCP Cloud SQL)で実行するとシンタックスエラーになることがありました。
原因はテーブル名のエイリアスの大文字・小文字を間違えていたという凡ミスでしたが、その時はじめてmacOSMySQLにおける大文字・小文字の扱いを意識したので調べたことを簡単ですがまとめます。

以下は調査したときの環境です。

MySQLにおける大文字・小文字の区別

公式ドキュメントの9.2.3 Identifier Case Sensitivityにありました。

MySQLにおいてデータベースはデータディレクトリ内のディレクトリ・ファイルに対応しているため、ファイルシステムが大文字・小文字を区別するかどうかが影響するそうです。
具体的にはデータベース名、テーブル名、テーブルのエイリアス、トリガー名が影響を受けます。

また、テーブル名とデータベース名(エイリアスも)についてはlower_case_table_namesというMySQLのシステム変数の影響も受けます。

以下は設定できる値と意味です。
※記載内容はMySQL5.7のドキュメントと同じだったので日本語訳してあるMySQL5.6のドキュメントから持ってきてます。

意味
0 テーブル名とデータベース名は、CREATE TABLE または CREATE DATABASE ステートメントで指定された大文字または小文字を使用してディスク上に格納されます。名前比較では大文字と小文字が区別されます。大文字小文字を区別しないファイル名を持つシステム (WindowsOS X など) で MySQL を実行する場合、この変数を 0 に設定しないでください。大文字と小文字を区別しないファイルシステムで --lower-case-table-names=0 を使用して強制的にこの変数を 0 に設定し、大文字と小文字を変えて MyISAM テーブル名にアクセスした場合、インデックスが破損することがあります。
1 テーブル名はディスク上に小文字で格納され、名前比較では大文字と小文字は区別されません。MySQL では、保存およびルックアップ時にすべてのテーブル名が小文字に変換されます。この動作はデータベース名やテーブルエイリアスにも適用されます。
2 テーブル名とデータベース名は、CREATE TABLE または CREATE DATABASE ステートメントで指定された大文字または小文字を使用してディスク上に格納されますが、MySQL ではルックアップ時に小文字に変換されます。名前比較では大文字と小文字が区別されません。これは大文字と小文字が区別されないファイルシステムでのみ機能します。InnoDB テーブル名は lower_case_table_names=1 のように、小文字で格納されます。

デフォルトの設定値は、UnixmacOS除く)は0、Windowsは1、macOSは2です。
大文字・小文字を区別するのはlower_case_table_names=0のみで、lower_case_table_names=2となっていたローカルのMacで0を指定してもMySQLは起動エラーとなりました。

Cloud SQLのOSが分かる情報は見つけられなかったですが、ふるまいから察するに大文字・小文字を区別するプラットフォームのようですね。

こういったプラットフォーム毎の違いによる問題を回避するために、一貫した命名規則を設けることを公式ドキュメントでは推奨しています。

macOSにおける大文字・小文字の区別

macOSはフォーマット時に大文字・小文字を区別するかどうかを選択できます。

Mac の「ディスクユーティリティ」で利用できるファイル・システム・フォーマットより抜粋。

macOS 10.13 以降を使用する Mac コンピュータに、以下の APFS ファイル・システム・フォーマットのいずれかを選択します。

  • APFS:APFS フォーマットを使用します。
  • APFS(暗号化):APFS フォーマットを使用し、ボリュームを暗号化します。
  • APFS(大文字/小文字を区別):APFS フォーマットを使用し、ファイル名およびフォルダ名の大文字/小文字を区別します。たとえば、「Homework」と「HOMEWORK」という名前のフォルダは、2 つの異なるフォルダです。
  • APFS(大文字/小文字を区別、暗号化):APFS フォーマットを使用し、ファイル名およびフォルダ名の大文字/小文字を区別し、ボリュームを暗号化します。たとえば、「Homework」と「HOMEWORK」という名前のフォルダは、2 つの異なるフォルダです。

新品のMacを購入したときは「APFS」になっているはずですし、いままでフォーマットするときも最初に選択してある「APFS」を無意識に選択していました。

ちなみに、「APFS(大文字/小文字を区別)」でフォーマットされていれば、MySQLlower_case_table_namesに0を設定できました。デフォルトで0が設定されます。

なお、大文字・小文字を区別しないファイルシステムmacOSAdobe製品をインストールできない問題があるようです。

以下はmacOS 10.12以前のファイルシステムであるHFSで発生した問題で、APFSでも発生するか試していないため不確かですが、「APFS(大文字/小文字を区別)」を使いたい場合は注意した方が良いかなと思います。

helpx.adobe.com

まとめ

MySQLの実態はディレクトリ・ファイルということを実感できる良い機会でした。
これまでなんとなくMySQLを使っていたんだと改めて感じました。

Delayed::JobのQueueをRedashのAlert機能で監視する

こんにちわ。開発部のdaikichi_3です。

弊社、一部システムにバックグラウンドで非同期処理を行うDelayed::Jobを導入しています。 バックエンドにActieRecordを選択しており、queueをMySQLで管理しています。 大量にqueueが溜まってしまうと、後続のqueueに影響を与えかねないので、監視したいという話になりました。

今回はqueueの溜まり具合をredashのAlerts機能で監視、通知を試した内容を記事にしたいと思います。

Redash とは

多様なデータソースとの連携が可能で、データ分析、可視化を行う為のツールで、オープンソースで提供されています。 2019年11月現在でサポートしているデータソースは40種程度です。

Redashには大きく分類して3つの機能があります。 連携したデータソースからデータを取得する「Query」、取得した結果の可視化を行う「Dashbord」、「Alert」です。 「Query」の結果を元にして「Dashbord」「Alert」の作成が可能です。

それでは早速Alertの作成をしたいと思います。

Query の作成

まずはAlertを作成する前に、Queryを作成します。 Queryの内容はDelayed::Jobのqueueが保存されるテーブル(delayed_jobs)のデータ件数を取得する簡易的なSQLです。

SELECT count(id) FROM delayed_jobs WHERE failed_at is NULL;

Queryの定期実行の為に、併せてスケジュール設定を行う必要があります。 スケジュール設定はQuery作成画面の左下にあります。

f:id:daikichi_3:20191127095207p:plain
スケジュール設定導線

Queryを5分毎に実行されるように設定します。

f:id:daikichi_3:20191127095302p:plain
スケジュール設定

設定が完了したらQueryをsaveします。

Alert の作成

作成したQuery結果の閾値、通知タイミング、通知内容、通知先の設定を行います。

f:id:daikichi_3:20191127095333p:plain
Alert作成

通知タイミング(When triggered, send notification)

3種類あります。実用性を考えると、「At most every」で任意の時間を設定するのが良いと思います。

  • Just Once : 閾値を超えた場合に一回通知される。
  • Each time alert is evaluated : 閾値を超えるたびに通知される。
  • At most every : 閾値を超えた場合に設定した通知間隔内に一回通知される。大量通知を防ぎ、計画的に通知したい場合。

通知内容(Template)

default,customの2種類あります。 custom templateだとRedashが用意している組み込みのテンプレート変数を使用できるので、ある程度の詳細はメッセージに設定可能です。 ここでは通知時にqueue数を知らせたいので、QUERY_RESULT_VALUE 変数を使用しています。

通知先(Destinations)

通知先にSlackのチャンネルを設定します。

f:id:daikichi_3:20191127095553p:plain
Destination設定

※ここで設定する通知先は事前に以下のように「Alerts Desitinations」から登録しておく必要があります。

f:id:daikichi_3:20191127095912p:plain
Alert Destinations

結果

Alert発生後、解消後のSlackへの通知結果です。 通知までの確認が簡単にできました。

f:id:daikichi_3:20191127100016p:plain
Alert 通知結果

弊社監視ツールにDatadogを使用していることもあり、当初はqueueの件数を取得するクエリを定期実行してDatadog側に送信することを考えたのですが、もっと手軽に実現したいという事でRedashのAlert機能を試してみました。
一度slackと連携しておけば、RedashのGUI上で完結できるので、手軽さという点だけであれば使えそうな気はしました。

参考

CircleCIでのユーザー管理について確認したこと

開発部でマネージャーをやっている @shrkw です。最近、lily58 liteを組んだので使っているんですが、右上隅のキーを押そうとしていつも隣の0を押してしまって悩んでいます。

さて、最近、CircleCIでのユーザー管理について確認したので、わかったことをここに記します。軽い記事をサクッと書こうというコンセプトのブログなのでこれでいいのです。

やりたかったこと

しばらくチームを離れる開発者がいたのでCircleCIへのアクセス権も外したかった

やり方

連携しているVCSでチームから外す、というのでOK.

  • VCS側でチームから除外すればCircleCIではログインできなくなる
  • 対象のユーザーがCircleCIにログインしようとした時にVCSへ確認が走り可否が判定される

期待と違ったこと

Settings -> Organization -> Usersで確認できるメンバーはVCS側から外しても反映されない。サポートに問い合わせればキャッシュを更新してくれるので消える。

問い合わせしない場合にいつ更新されるかはわかったら教えてもらうようにサポートにお願いしている。

課金対象となっているユーザーの確認

Settings -> Plan Usage -> Users タブ

から詳細を確認できる。

課金対象となる条件

一度でもコミットもしくはAPI、UI等でビルドをトリガーさせること

となっているけど、10月の課金システム変更で利用シートをあらかじめ設定することになったので、上限を超えたユーザーのビルドはブロックされて起動しないようだ。

“This job has been blocked because no user seats are available on your plan. Please purchase more user seats to continue building.” – CircleCI Support Center

これは同僚が教えてくれた

なので、VCS上のチームメンバーと関係なく、実際に利用するユーザー数でシートを設定しておくので良さそう。

時間のかかるGCFへのデプロイをShellスクリプトで並列化する

こんにちは。エンジニアの @umanoda です。弊社でも開発者ブログを始めることになりました。よろしくおねがいします。

最近、新たに社内システムを構築するのにGCPを使いました。まだまだシステム運用面でできていないことも多く、例えばGoogle Cloud Functions(GCF)へのコードデプロイをローカルマシンのコンソールから手動で行っています。

# デプロイコマンド
gcloud functions deploy func_a

しかし、このコマンド実行は終わるのに5分くらいかかります。
ごくたまに複数のFunctionのデプロイをしたいときがあるのですが、前のデプロイを待って次のコマンドを実行する……というやり方をすると時間がかかり非常に手間です。

gcloud functions deploy func_a      # デプロイが実行され、画面にいろいろ表示される
gcloud functions deploy func_b      # func_aの完了を待ってから実行する…つらい

CIを使い、1-Function 1-Jobで処理させるなどすれば同時に複数のデプロイができて早そうですが、それを構築するほどの規模でもない……ということでShellを使って並列にデプロイを行いました。Shellにはパイプラインなどの並列処理をさせる仕組みが色々あって好きなのですが、バックグラウンド処理をつかった並列処理もなかなか便利だと感心したので記事にしてみます。

GCPへの並行デプロイ

デプロイするにあたり、以下のような前提がありました

  • 複数のGCPプロジェクトに、同じFunctionをデプロイしたい
  • プロジェクトによっては func_a はデプロイ済みだが、 func_b はデプロイしたくないというのがある。すでにGCFにデプロイ済みのFunctionのみを再デプロイしてアップデートしたい

これを以下のShellスクリプトでデプロイします

# my-project-1  .. GCPプロジェクト名. func_a, func_bがある
# my-project-2  .. func_a, func_b, func_zがある
# my-project-3  .. func_cだけある
projects=$(cat << EOS
my-project-1
my-project-2
my-project-3
EOS
)

cmd="echo start"

for project in $projects ; do
  for func in $(gcloud functions list --project=$project | grep ACTIVE | cut -f 1 -d" ") ; do
    cmd="$cmd & gcloud functions deploy --project=$project $func"
  done
done
sh -c "$cmd"

これを実行すると

echo start & \
gcloud functions deploy --project=my-project-1 func_a & \
gcloud functions deploy --project=my-project-1 func_b & \
gcloud functions deploy --project=my-project-2 func_a & \
gcloud functions deploy --project=my-project-2 func_b & \
gcloud functions deploy --project=my-project-2 func_c & \
gcloud functions deploy --project=my-project-3 func_c

というコマンドがShellスクリプト内部で実行されます。最後のコマンドはあえてバックグラウンドで実行しないようにしています。このShellスクリプトを呼んだ時にデプロイが終わるのを待つためです。
最後のフォアグラウンド処理が終わっても、その他のバックグラウンド処理が終わっているとは限らないのですが…まあ同じような処理行っているのだし最後に実行したものが最後に終わるだろうという楽観的にみこんでいます。

すべてのデプロイが終わっているか確認したければ ps | grep gcloud functions で確認できます。

書き捨てのつもりで上記のShellスクリプトを雑に作ったのですが、別解として xargs-P オプションで並列実行する事もできるねという話を同僚から教えてもらいました。以下のように xargs を使えば並列にデプロイを実行しつつ全プロセスの完了を待ってくれるのでより良いのだろうと思います。

for project in $projects ; do
  for func in $(gcloud functions list --project=$project | grep ACTIVE | cut -f 1 -d" ") ; do
    cmd="$cmd --project=$project $func\n"
  done
done
echo -e $cmd | xargs -n 2 -P 0 gcloud functions deploy

Ref. https://linuxjm.osdn.jp/html/GNU_findutils/man1/xargs.1.html

& を使ったコマンドの並列実行

上記スクリプトについて補足します。
Shellで複数の処理を並列実行するのは非常に簡単です。 command & の形式でバックグラウンドでコマンド実行することができますが、これを command1 & command2 & ... のように記述するだけでそれぞれが並列に実行されます。
実はこれは、単に複数のバックグラウンド実行をワンライナーで書いただけだったりします。

command1 &
command2 &

#  上と同じ
command1 & command2 &

今回作ったのスクリプトのように機械的にコマンドを作るときはワンライナーでやると楽なことが多いです。最初に出したスクリプトのように sh コマンドで実行できたり、 実行する前に echo をコマンドの先頭に入れて内容を確認できたりするのが地味に便利。

本当に並列実行されているのか実験してみましょう。
時間がかかる処理 slow_proc を作って、バックグラウンド実行します。

# background.sh

# なんか時間がかかる処理
slow_proc () {
  ruby -e 'print("Start:", Process.pid, "\n"); sleep(rand); print("Finish:", Process.pid, "\n")'
}

slow_proc & slow_proc & slow_proc

実行すると以下のような出力になり、 slow_proc が別々のプロセス上で並列処理されている事がわかります。いいですね。

$ bash background.sh 
Start:34486
Start:34488
Start:34487
Finish:34488
Finish:34487
Finish:34486

なお前述したとおり command1 & command2 が終了した場合、command2の完了は保証されていますが、必ずしも command1 が完了しているとは限らないことには注意してください。

終わりに

Shellを使うとこのように、並列実行できるスクリプトが手軽に書けるので便利です。もっとも、長期的にShellスクリプトを使って運用してしていくと、コードベースが大きくなっていった時に辛くなりがちなので、ワンタイムスクリプトとして便利使いするのがちょうどよいでしょう。
なお今はこのようにデプロイをしましたが、おいおいは Cloud Build を使って手動でのデプロイそのものを無くしていきたいと思っています。

こんな感じで、ぬるっと Viibar Engineering Blog が始まりました。次回もお楽しみに。