FunctionComputeでもCICDしたい!


この記事はAlibaba Cloud Advent Calendar 2019 11日目の記事です。

はじめに

FunctionComputeでもインフラをコードで管理したいし、テストが成功したらAPIを自動でデプロイしてほしいですよね。
今ならTerraformも対応しているようだけど、公式ドキュメントで紹介されているFunを使ってCICD環境を構築できないだろうか。
そういえば、CIツールとして先日公式リリースになったばかりのGithubActionsがちょっと気になっていたのだった。
という思惑の元、CICDを通してHelloWorldするまでの記録です。

システムアーキテクチャ

AWSLambda等でもよく見る構成で、認証もついてない雑なAPIです。

今回触れないこと

サンプルのAPIはNode.jsで用意していますが、実装とテストの中身については本項では省略します。
FunctionComputeにデプロイするAPIの実装は、ただのHelloWorldでも発火トリガーの種類や、APIGatewayと組み合わせるのかで実装が変わってくるのですが、こちらの説明も省略します。

Funについて

Funは、AlibabaCloudのコミュニティで開発されているサポートツールです。
FunctionComputeとAPIGatewayの環境をYAML形式のファイルで定義してローカルからコマンドを実行すると、AlibabaCloud上に定義通りに環境を生成してくれます。Terraformでやれることと一緒ですね。
ただ、Terraformと違って環境を壊したりはできないみたいなので、お試しで作った環境をWEBコンソールまで削除しに行かないといけないのが少し面倒です。
また、FunはFunctionComputeの環境を構築すると同時に、APIの実装もデプロイします。
実行ランタイムにNodejsを選択していた場合、node_modulesもまとめて圧縮してアップロードしてくれます。

なお、GitHubリポジトリのREADMEが中国語なのを嫌って公式日本語ドキュメント通りに設定を進めるとうまく動きませんでした。よく見るとREADMEとは記載が微妙に異なっていて、Funの開発が積極的に動いているので、公式ドキュメント(中国語の公式ドキュメントが整ってから反映される日本語ドキュメントは特に)の更新が遅れているようです。

基本操作

README読めばわかりますが…
npm install @alicloud/fun -gでインストール
fun configでAlibabaCloudのリソースへのアクセス権限を設定してから、fun deployでデプロイできます。
プロジェクトリポジトリのルートに.envファイルを予め用意することで、fun configの実行は省略可能でした。

成果物

手っ取り早くモノが見たいという方のためのリポジトリ

手順

  1. APIの実装とテストを用意する
  2. FunのAlibabaCloudリソースへのアクセス権限設定に必要な情報を用意
  3. Funの設定ファイルを用意
  4. Actionsの設定ファイルを用意
  5. GithubにPushしてActionsの結果を確認
  6. AlibabaCloudのWEBコンソールからデプロイ結果を確認
  7. 外部からアクセスできるか確認

1. APIの実装とテストを用意する

本体

hello.js
'use strict';

module.exports.handler = function(event, context, callback) {
  const response = {
    "isBase64Encoded": false,
    "statusCode": 200,
    "headers": { "Content-Type": "application/json" },
    "body": "hello,world!"
  }
  callback(null, response);
}

テスト

test/hello_test.js
const expect = require('expect')
const main = require('../hello.js')

const hoge = (sys, res)=> {
  result = res.body
}

let result = ""

describe('HelloTest', function(){
  it ('promise', () => {
    main.handler({}, "", hoge)
    expect(result).toBe('hello,world!')
  })
})

2. FunのAlibabaCloudリソースへのアクセス権限設定に必要な情報を用意

  • AlibabaCloudのAccountID

こちらはRAMアカウントではなく、ROOTアカウントの物が必要になります。

  • RAMユーザーとAK

ResourceAccessManagementで、Webコンソールログインが出来ないFun用のユーザーとAKを作成しました。
権限はRAM、APIGateway、FunctionComputeのFullAccess権です。

RAMのアクセス権は要らないと思うんだけど、これをつけないとFunコマンドを実行した際にRAM関連のエラーメッセージがでました。なんのために必要なんだ…???

また、アクセスログを取るためにLogServiceを使うなど、別サービスの設定をFunに追加する場合は、適宜このRAMユーザーに利用するサービスの権限を付与する必要があります。

3. Funの設定ファイルを用意

template.yml
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  CICDSampleFunctionCompute:
    Type: 'Aliyun::Serverless::Service'
    CICDSample:
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: hello.handler
        CodeUri: './'
        Description: 'return string "hello,world!"'
        Runtime: nodejs10
        Timeout: 60
  CICDSampleAPIGateway:
    Type: 'Aliyun::Serverless::Api'
    Properties:
      StageName: TEST,RELEASE
      DefinitionBody:
        '/hello':
          get:
            x-aliyun-apigateway-api-name: sampleCICD
            x-aliyun-apigateway-fc:
              arn: acs::fc::service/${CICDSampleFunctionCompute.Arn}/functions/${CICDSample.Arn}/
              timeout: 10000
            x-aliyun-apigateway-request-config:
              requestMode: 'Mapping'
              requestProtocol: 'http'

4. Actionsの設定ファイルを用意

github/workflows/nodejs.yml
name: Node CI

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [8.x, 10.x, 12.x]

    steps:
    - uses: actions/checkout@v1
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - name: npm install, build, and test
      run: |
        npm ci
        npm run build --if-present
        npm test
    - name: install funcraft
      run: |
        npm install @alicloud/fun -g
    - name: fun config echo to .env
      run: |
        echo ACCOUNT_ID=${{ secrets.ALICLOUD_ACCOUNT_ID }} > .env
        echo REGION=ap-northeast-1 >> .env
        echo ACCESS_KEY_ID=LTAI4Ftiwv1igVS2Whng3bfA >> .env
        echo ACCESS_KEY_SECRET=${{ secrets.ALICLOUD_TOKEN }} >> .env
        echo TIMEOUT=60 >> .env
        echo RETRIES=3 >> .env
    - name: deploy to AlibabaCloud FunctionCompute
      run: |
        fun deploy

FunのConfig設定は愚直に必要な項目を.envにechoしているんですが、これはもう少しなんとかならないかなぁと思います。
ACCESSKEY_SECRETなどの秘匿情報は、GithubのリポジトリSettingから名前をつけて設定しておくことで、 ${{secret.NAME}}の形式で環境変数的に引っ張ってこれます。

5. GithubにPushしてActionsの結果を確認

ここで問題なのは、FunでDeployに失敗した場合、ErrorではなくAlibabaCloudからのレスポンスを表示するだけなので、GithubActionsとしてはグリーンになってしまう点です。
GithubActionsの結果だけ見てデプロイ出来たか否かがわからないのは大きい問題だと思います。なんとか判別する方法はないだろうか…(宿題)

6. AlibabaCloudのWEBコンソールからデプロイ結果を確認

設定ファイルで指定した通り、テストと本番で実行中になっています。

7. 外部からアクセスできるか確認

APIGatewayのAPIグループからInternetSubdomainを確認します。これはAlibabaCloudがデフォルトで提供してくれるサブドメインです。画像に赤字であるように、1日1000回のアクセス制限があるのでサービスで採用する場合は独自ドメインをバインドしたほうがよいでしょう。なお、この独自ドメインのバインドもFunから設定できます。

http://InternetSubdomain/helloで、hello,world!がレスポンスされることが確認できました。

おわりに

無事FunctionComputeでCICDできました。
インフラをコードで管理するの楽しいですし、自動化のための一歩を踏み出した感じでいいですよね。

今回GithubActionsを初めて使ってみて、CircleCIより設定も楽で、なによりリポジトリから直接お試しにいけるっていうのが、ハードル低くていいなと思いました。
Funには今回は試していないけど、実運用するなら考えなければいけないログとかドメインとかの部分に関する設定項目もたくさんあります。中国語でハードル高いですが、設定の変数名を眺めるだけでもなにができそうかはわかるので、ぜひ一度目を通してみてください。
APIGatewayの設定はSwaggerファイル指定も出来るので、APIをOASで管理するプロジェクトも多いみたいだし、この点は選択時の強みになるんじゃないでしょうか。Terraformでも同じことできるかどうかは知らないけど。