[2018/10/9追記] 現在この記事で紹介されている方法は、Ruby、fastlaneともにかなり古いバージョンを使用しているため、実際の環境では使わないでください
この記事は ハンズラボ Advent Calendar 2017 1日目の記事です。
こんにちは。ハンズラボでアプリケーションエンジニアをしている @mii-chan こと三井田です。今年は新卒研修や東急ハンズのAlexaスキルの開発 (途中まで) などを担当し、現在はSwift 4で業務用iOSアプリを新規開発しています。
今回はiOSアプリを開発する上でかかせない、iOSアプリの証明書とプロビジョニングプロファイルについての話をします。
iOSアプリの証明書とプロビジョニングプロファイルの有効期限忘れがち問題
忘れずに更新が必要なiOSアプリの証明書とプロビジョニングプロファイルですが、証明書やプロビジョニングプロファイルの数が多くなってくると、全てを定期的にチェックするのは中々の労力を要します。
そこで当時新卒1年目だった私は「有効期限が後何日で切れるかを自動でSlackに通知すればいいんだ!」と考え、毎週自動でSlackに証明書・プロビジョニングプロファイルの有効期限を通知するBotを開発しました。
iOSアプリの証明書とプロビジョニングプロファイルの有効期限が後何日で切れるかを毎週自動でSlackに通知してみた
しかし、このBotを運用していくうちに、いくつかの課題が出てきました。
旧アーキテクチャの問題点
開発したBotは、cronでシェルスクリプトを定期実行し、指定のGitリポジトリにPushされた証明書・プロビジョニングプロファイルを取得後、その有効期限をSlackに通知するというものでした。
このアーキテクチャの問題点は以下です。
GitリポジトリにPushされていない証明書・プロビジョニングプロファイルの有効期限は通知されない
指定のGitリポジトリへのPushは手動での作業となっていたため、Apple Developer Portal上にはあるけどGitリポジトリ上にはないという状態が発生する可能性があります。もしGitリポジトリにPushされていないものがあった場合、このBotではそれを救う術はありません。
「Apple Developer Portalから全ての証明書とプロビジョニングプロファイルの情報を取得できれば最高なのにな〜」と思っていたら、弊社iOSエンジニア駒場から「それspaceship
でできるよ」みたいな助言をもらったので、今回はこれを使って新しいBotを開発してみました。
新アーキテクチャ
上のようなアーキテクチャに刷新しました。かの有名なfastlane
の一部であるspaceship
を使えば、Apple Developer Portalへのログイン、証明書・プロビジョニングプロファイルの情報取得が可能です。更にこのためだけにEC2を常時稼働させるよりも、CloudWatch EventでAWS Lambdaを定期実行させた方が運用面でもコスト面でも良いと考えたため、AWSのブログ記事を参考に、Node.jsのexec
コマンドを使ってAWS Lambda内でRubyスクリプトを実行させるようにしてみました。
ここからは、このBotをどのように開発したかを順に話していきます。
(今すぐ最終成果物が見たい、という方はこちらまで飛んでください)
1. Dockerを使ってRubyの実行環境の構築を行う
まずは、AWS LambdaでRubyを実行するための環境を構築します。Linux上でコンパイルを行う必要があるため、EC2上で環境構築を行います。前述のAWSのブログ記事ではEC2(t2.large)インスタンスを立ち上げて作業を行っていますが、今回はAmazon LinuxのDockerイメージを使って環境構築を行います。
前提 : ローカル環境にDockerがインストールされており、起動していること
最初にプロジェクトフォルダを適当に作ります。
今回は、プロジェクトフォルダ内のruby-env
ディレクトリにRubyの実行環境を入れます。
$ mkdir ios-cer-profile-expiration-date-checker
$ cd ios-cer-profile-expiration-date-checker
$ mkdir ruby-env
$ cd ruby-env
Amazon LinuxのDockerイメージを使う
下記のコマンドでAmazon LinuxのDockerイメージを持ってきます。
$ docker pull amazonlinux:latest
持ってこれたらdocker run
しましょう。
ホスト側のカレントディレクトリと、コンテナ内の/var/task
を共有しておきます。
docker run --rm -v "$(pwd)":/var/task -it amazonlinux:latest
Rubyのインストール
今回はAWSのブログ記事と同じくTraveling Ruby
を使います。
最初に、yum
のアップデートとRubyのインストールに必要なパッケージのインストールを行います。
$ yum update -y
$ yum -y groupinstall "Development tools"
$ yum -y install openssl-devel zlib-devel
$ yum -y install wget
インストールが終了したら、Traveling Ruby
をwget
で取ってきて/var/task
下に解凍します。
$ wget http://d6r77u77i8pq3.cloudfront.net/releases/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz
$ tar -xvf traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz -C /var/task
$ cd /var/task
でディレクトリに移動後、以下のコマンドでRubyが正しくインストールされたかを確認します。
$ ./bin/ruby -v
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
正しくインストールされています。
このままGem
なりBundler
なりでインストールすれば実行環境が構築できそうなのですが、Traveling Ruby
にはヘッダーファイルが同梱されていないため、そのまま./bin/gem
でfastlane
のインストールを行うと、unf_ext-0.0.7.4.gem
のインストールで失敗してしまいます。
/var/task/bin/ruby -r ./siteconf20171127-14946-1kqallk.rb extconf.rb
mkmf.rb can't find header files for ruby at /var/task/lib/ruby/include/ruby.h
そこで、Systemに同じバージョン(2.2.2)のRubyを入れ、それを使ってfastlane
をインストールすることにしました。
$ wget http://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.2.tar.gz
$ tar -xvf ruby-2.2.2.tar.gz
$ cd ruby-2.2.2
$ ./configure && make && make install
fastlane(とslack-incoming-webhooks)のインストール
準備が整ったところで、fastlane
とslack-incoming-webhooks
(Slack通知用)をBundler
を使ってインストールしましょう。
ここで、AWS Lambdaのデプロイメントの制限について少し考えます。公式ドキュメントによると、Lambda 関数デプロイパッケージのサイズ (圧縮 .zip/.jar ファイル)
のデフォルトの制限は50 MB
、デプロイパッケージ (非圧縮 .zip/.jar サイズ) に圧縮できるコード/依存関係のサイズ
のデフォルトの制限は250 MB
との記述があります。つまり、圧縮前のパッケージサイズが250MB
、圧縮後のサイズが50MB
を超えてはいけないという制限です。
通常は大丈夫だと思いますが、今回のようにRubyの実行環境ごとLambdaにアップロードする場合だとパッケージのサイズが大きくなってしまうため、この制限を考慮に入れながら環境構築を進める必要があります。
今回インストールするfastlane
ですが、fastlane-2.60.1
(46.6MB)からfastlane-2.61.0
(60.3MB)で 13.7MB くらいサイズが大きくなったため、2.61.0
以降をインストールした場合、デプロイパッケージを圧縮した後のZipファイルのサイズが50MB
を超えてしまいました。
そこで、今回はバージョン2.60.1
をバージョン指定でインストールします。
まずは、Traveling Ruby
のgem
とシステムのgem
にBundler
をインストールします。
$ cd /var/task
$ ./bin/gem install bundler --no-document
$ gem install bundler --no-document
Gemfileを作成しましょう。
$ bundle init
ホスト側でGemfileを以下のように編集します。
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
gem "fastlane", "2.60.1"
gem "slack-incoming-webhooks"
編集後、コンテナ上でSystemにインストールしたBundler
を使ってfastlane
とslack-incoming-webhooks
をインストールしましょう。
$ bundle install --path vendor/bundle
以上でRubyの実行環境の構築は終わりです。ホスト側のカレントディレクトリにRubyの実行環境ができているはずです。
*ここまでの手順をまとめたDockerfile
あります。
https://github.com/mii-chan/ios-cer-profile-expiration-date-checker/blob/master/templates/Dockerfile
2. Lambda関数の作成
次にLambda関数を作成します。
まずは、Rubyのスクリプトです。肝となるのは以下の部分です。
require 'spaceship'
<中略>
Spaceship::Portal.login(id, pass)
cer_profile_list = { CERTIFICATE => Spaceship::Portal.certificate.all, MOBILEPROVISION => Spaceship::Portal.provisioning_profile.all }
fastlane/spaceshipをインポートし、Apple IDとパスワードでApple Developer Portalにログインします。ログイン後は、Spaceship::Portal.certificate.all
で全ての証明書、Spaceship::Portal.provisioning_profile.all
で全てのプロビジョニングプロファイルの情報を取得することができます。
その後、後何日で有効期限が切れるかを計算し、slack-incoming-webhooks
を使ってSlackへの通知を行なっています。
次に、このスクリプトを実行するNode.jsスクリプトを書きます。
SlackのWebhook URL、Apple ID、PasswordはLambdaの環境変数に設定していますが、これらはKMSを使って暗号化しましょう。
const KMS = new AWS.KMS();
<中略>
const encryptedParams = [
process.env['WEBHOOKURL'],
process.env['ID'],
process.env['PASS']
];
const decryptedParams = {};
暗号化されたキーは、KMSのdecrypt
を使ってLambda関数実行時に複合化します。
その後、exec
コマンドを使ってNode.jsからRubyスクリプトを実行するという流れです。
これでLambda関数の作成が完了しました。
後はLambdaアップロード用のZipファイルを作成して、Lambda関数の新規作成としかるべき設定をすれば完成です。。
が、コンソールでポチポチLambdaを新規作成するのは手間なので、今回はLambda関数作成その他諸々の環境をAWS CloudFormation、AWS SAMを使って自動で構築できるようにしました。
3. 自動化しちゃうぞ
Lambda関数構築自動化のため、以下のテンプレートを用意しました。
デプロイパッケージをアップロードするS3バケット、環境変数を暗号化するためのKMS、Lambdaの実行ロールを作成するCloudFormationテンプレート
https://github.com/mii-chan/ios-cer-profile-expiration-date-checker/blob/master/templates/cf-template.yaml
Lambda関数をデプロイするSAMテンプレート
https://github.com/mii-chan/ios-cer-profile-expiration-date-checker/blob/master/templates/sam-template.yaml
最終成果物
最終的にできたものは以下に置いてあります。
https://github.com/mii-chan/ios-cer-profile-expiration-date-checker
下の画像のように、証明書・プロビジョニングプロファイルの有効期限が残り何日で切れるのかをSlackに通知してくれます。
自動構築の手順は以下です。
1. Rubyの実行環境をローカルに持って来る
前提条件:Dockerが起動していること
プロジェクトルートで以下のコマンドを実行します。
$ ./scripts/create-ruby-env.sh
このスクリプトでdocker build
、docker run
を行います。
自動的にコンテナに入るので、コンテナ内で以下のコマンドを実行します。
bash-4.2# cp -rp * .[^\.]* /app
bash-4.2# exit
Rubyの実行環境がruby-env
ディレクトリ配下にあることを確認してください。
2. 各種パラメータの設定
parameters
ディレクトリにある2つのファイルに書かれているパラメータを、お好きな値に変更します。
parameters.json
Parameter Key | Parameter Value Example | Description |
---|---|---|
StackName | "iOS-cer-profile-expiration-date-check" |
Lambdaをデプロイする事前準備で作成されるスタックの名前(アルファベットとハイフンしか使えないので注意!)。S3バケット、KMS、Lambdaの実行ロールの作成を行う。詳しくはtemplate ディレクトリ内のcf-template.yaml参照 |
StackNameLambda | "iOS-cer-profile-expiration-date-check-lambda" |
Lambdaのデプロイで作成されるスタックの名前(アルファベットとハイフンしか使えないので注意!)。詳しくはtemplate ディレクトリ内のsam-template.yaml参照 |
CHANNEL | "#general" |
通知先のSlackのChannel |
USERNAME | "iOS Monthly Bot" |
Slack通知を行うBotの名前 |
ICON | ":iphone:" |
Slack通知を行うBotのアイコン |
WARNINGDAY | "60" |
残り日数がこの日より少なくなると、Slack通知時のAttachmentの色がオレンジになります |
DANGERDAY | "30" |
残り日数がこの日より少なくなると、Slack通知時のAttachmentの色が赤になります |
schedule-expression
Lambdaを起動させるScheduled Eventのスケジュール式をここで指定します。ファイルの5行目に書かれているようなScheduleExpression: <スケジュール式>
の形で指定してください。スケジュール式についてはこちら(公式ドキュメント)をご参照ください。
3. Lambdaの構築
前提条件:
- AWS CLIがインストールされていること
- 割と強めのIAMユーザーのクレデンシャルがセットされていること(実行に必要なポリシーは後日まとめます...)
- Apple Developer PortalにログインするためのAppleID、Password、SlackのWebhook URLを知っていること
プロジェクトルートで以下のコマンドを実行します。
$ ./scripts/setup.sh
実行時にプロンプトで以下の入力を求められるので、然るべき値を入力してください(KMSで暗号化された後、Lambdaの環境変数にセットされます)。
-
AppleID
: Apple Developer PortalにログインするためのApple ID -
Password
: Apple Developer PortalにログインするためのPassword -
WebhookURL
: SlackのWebhook URL
スクリプトが正常終了すると、Lambdaがアカウントにデプロイされているはずです。
注意事項
- デプロイ後、コンソールでSlackのWebhook URLを設定し直す際は、
https://
を除いた状態(hooks.slack.com/xxx
)で環境変数に設定してください(暗号化もお忘れなく) - スクリプト実行途中でエラーが起こってしまった場合は、エラーメッセージが表示されると思うので、修正して再度スクリプトを実行してください。
- Stackが中途半端に作成された場合は、作られたStackを削除してから再実行した方がよいかもしれません
-
scripts/delete-all-stacks.sh
を実行すれば、全てのStackを削除できます。
実際にやってみて
-
Traveling Ruby
を使ったので、Rubyのバージョンが2.2.2- 新しいバージョンを使いたい...
- 2.4系のソースコードをコンテナに落としてきて
/var/task
以下にインストールしてみたけどLambda関数の実行が上手くいかず...
-
Lambdaデプロイパッケージ(圧縮)
50M
問題- 最新の
fastlane
がアップロードできない...
- 最新の
-
それならいっそ、最新のRubyとfastlaneを入れたDockerイメージを作って、Lambdaの定期実行でECSを起動・停止させればよかったのでは?
- やった後に思いました。未検証
今回は気の赴くままにやってみましたが、スクリプトの実行をどのようなアーキテクチャで行うかについては、引き続き検討を続けていく必要がありそうです。
しかしながら、fastlane/spaceship
を使えば本当に簡単にApple Developer Portalから証明書・プロビジョニングプロファイル情報を取得できたので、今後はこのRubyスクリプトを何らかの形で実行して、全てのiOSアプリの証明書・プロビジョニングプロファイルの更新漏れを引き続き防いでいきたいと思います。
まとめ
AWS Lambdaで (Goだけじゃなくて) Rubyもサポートしてほしい!
ハンズラボ Advent Calendar 2017 2日目の明日は、この前まで一緒にAngularで開発をしていた @daikiojm くんです!
お楽しみに〜
参考
Scripting Languages for AWS Lambda: Running PHP, Ruby, and Go
https://aws.amazon.com/jp/blogs/compute/scripting-languages-for-aws-lambda-running-php-ruby-and-go/
MacでDockerとAmazon Linuxコンテナを使ってAWS Lambda (Node.js-v4.3.2)にデプロイしてみた
https://qiita.com/tomiyan/items/be1ccb20b154cabb8c7c
【AWS Lambda】Amazon Linux の Docker イメージを使ってデプロイパッケージを作成する
https://qiita.com/morishin/items/cfba9ed41a73158b38f6
CentOS6に新しいバージョンのrubyをインストールする手順
http://zacodesign.net/blog/?p=1952
[AWS CloudFormation] AWS リソースプロパティタイプのリファレンス
http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html
awslabs/serverless-application-model / versions / 2016-10-31.md
https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md