Github動作を用いたAWS上のServerless MonoreposアプリケーションのためのセットアップCI/CD


多くの言語でサーバアプリケーションを書く方法がたくさんあります.Goangのようなサーバーを構築するのに役立つフレームワークがたくさんあります.Javaでは、Javaのようなサーバーを立ち上げます.ServerLessは、開発者がサーバーを管理することなくアプリケーションを構築し実行することができるクラウドネイティブ開発モデルです.したがって、Serverlessなアーキテクチャモデルを使用することで、サーバーのスケーリングを管理するプロビジョニング、セキュリティパッチの更新、誰かのサーバーへのハッキングなどの心配をする必要はありません.これらすべては、クラウドプロバイダによって扱われます.それで、我々は可能な限りAPIのためのServerlessなアーキテクチャを使用しようとするべきであると言うことができます.
このポストでは、APIアプリケーションとしてJavaScript関数を実行することについて話します.

アムスラムダ
AWSラムダは私たちのJS関数を展開するために使用されるServerlessな計算サービスです.我々は、ZIPファイルとラムダとして我々のコードをアップロードして、正確に計算実行力を割り当てて、交通のどんなスケールのためにも来る要求またはイベントに基づいてコードを実行します.
Nodejs Serverlessなアプリケーションを書くために利用できる多くのフレームワークがありますArchitect , Up , Middy そして、より多く.私たちはServerless Framework 我々のバックエンドAPIアプリケーションを書くために、我々は複数のプログラミング言語を使用することができます、そして、我々はS 3、APIゲートウェイとServerlessフレームワークのような複数の他のAWSサービスを使用しています.

無セルフレームワーク
Serverlessフレームワークでは、AWSラムダ関数を開発し、配備することができます.それは構造、オートメーションとベストプラクティスをボックスから提供するCLIです.そして、あなたが機能とイベントから成る洗練された、イベント駆動、Serverlessな建築を構築することに集中することができます.

セットアップ
必要条件
  • ノード名:> 12
  • Serverless CLI :コマンドをインストールするnpm install -g serverless
  • 私たちは以下のファイル/フォルダ構造を使用して簡単なNodeJSアプリケーションを書くか、または実行することができますserverless コマンドとセットアップから新しいプロジェクトを設定します.からのデモコードをクローン化することができますhttps://github.com/thakkaryash94/aws-serverless-ci-cd-demo .
    .
    ├── handler.js
    ├── package-lock.json
    ├── package.json
    └── serverless.yml
    
    ハンドラ.js
    'use strict';
    
    module.exports.main = async (event, context) => {
      return {
        statusCode: 200,
        body: JSON.stringify(
          {
            message: 'Hello from new service A!',
            input: event,
          },
          null,
          2
        ),
      };
    };
    
    我々は更新されますserverless.yml 下記のファイル.私たちはAWS S 3を使用して、API URLとしての機能にアクセスするためのZIPとAWS APIゲートウェイサービスを格納します.
    無力.気象研
    service: servicea
    
    frameworkVersion: "2"
    
    provider:
      name: aws
      runtime: nodejs14.x
      lambdaHashingVersion: 20201221
      region: ${env:AWS_REGION}
      apiGateway:
        restApiId: ${env:AWS_REST_API_ID}
        restApiRootResourceId: ${env:AWS_REST_API_ROOT_ID}
      # delete below section if you don't want to keep the Lambda function zip in a bucket
      deploymentBucket:
        blockPublicAccess: true # Prevents public access via ACLs or bucket policies. Default is false
        skipPolicySetup: false # Prevents creation of default bucket policy when framework creates the deployment bucket. Default is false
        name: ${env:AWS_BUCKET_NAME} # Deployment bucket name. Default is generated by the framework
        maxPreviousDeploymentArtifacts: 10 # On every deployment the framework prunes the bucket to remove artifacts older than this limit. The default is 5
    
    functions:
      main:
        handler: handler.main # Function name
        memorySize: 128
        events:
          - http:
              path: servicea # URL path to access the function
              method: get # Method name for API gateway
    
    さて、以下のコマンドを実行してローカル関数を実行できます.それはあなたのAWSSIGN地域のような環境変数の不足についての警告を与える、AWSRUNE BACKCKHERN名などが、我々はそれらを無視することができますか、ローカルの開発目的のためにだけでなく、それらをコメントすることができます.
    $ serverless invoke local -f main
    
    以下のように応答を返します.
    {
        "statusCode": 200,
        "body": "{\n  \"message\": \"Hello from new service A!\",\n  \"input\": \"\"\n}"
    }
    
    それで、それは我々のServerlessなアプリケーションが準備ができて、正しくローカルに働くことを意味します.実際のプロジェクトでは、これらのアプリケーションの多くが必要になります.したがって、各アプリケーションは、個々のAPI要求に対応します.
    GTHUBやGITLAB、BitbucketなどのVCSプロバイダにコードを保管しています.問題は、一つのプロジェクトに対しても、以下のような多くのリポジトリを維持することは非常に困難です.それで、それを修正するために、我々は1つの倉庫でこれらのすべてのアプリケーションを保存することができます.それはmonorepoとmultirepoの違いです.

    セットアップ
    Serverlessを変換するには、フォルダを作成する必要があります.aws-serverless-ci-cd-demo そして、その中のフォルダサービスを動かしてください.現在、我々は我々が我々のプロジェクトのために必要とする多くの機能を持つことができます、そして、彼ら全員は一つの倉庫の中にあります.
    最終的な全体構造は以下のようになります.
    aws-serverless-ci-cd-demo
    ├── README.md
    ├── servicea
    │   ├── handler.js
    │   ├── package-lock.json
    │   ├── package.json
    │   └── serverless.yml
    └── serviceb
        ├── handler.js
        ├── package-lock.json
        ├── package.json
        └── serverless.yml
    
    AWSラムダ、AWS S 3、APIゲートウェイを利用してサービスを展開し、アクセスします.AWS S 3とAPIゲートウェイの使用について議論しましょう.

    AWS 3
    Amazon Simple Storage Service(Amazon S 3)は、業界をリードするスケーラビリティ、データ可用性、セキュリティ、およびパフォーマンスを提供するオブジェクトストレージサービスです.
    ラムダ関数コードをバケツのジップとして保存します.利点は、コードと日付時刻でラムダ関数のトラックを保つことができます.それは完全に任意のステップです.あなたがコメントしてスキップすることができますdeploymentBucket Serverlessのコード.YMLファイル.

    APIゲートウェイ
    AmazonのAPIゲートウェイは、開発者が作成、公開、維持、監視、および任意の規模でAPIを確保するために簡単に完全に管理サービスです.Apisはアプリケーションのためのフロントドアとして機能します.
    私たちは、私たちがそれにアクセスできるように、我々のラムダ関数をURLに公開するためにAPIゲートウェイを使用します.サーバレスフレームワークはpath , method HTTPセクションとセットアップルートからそれに基づいて.デモでは、それは/servicea 路線図GET メソッド.Serverlessフレームワークは、ラムダ関数でこのルートをマップします.

    CI/CD展開
    Ci/Cdの“ci”は連続した統合を意味します.そして、それは開発者がコードを押すとき、コードをテストしました.我々のアプリケーションでは、関数のzipファイルを作成します.
    Ci/Cdの「CD」は連続的な配送および/または連続的な展開を意味します.そして、それは開発、ステージング、UAT、QA、生産環境にコードを届けて/配備することを意味します.アプリケーションでは、ZIPをラムダ関数に展開し、APIゲートウェイを設定することを意味します.
    幸運にも、Serverlessなフレームワークはそれをサポートしています.それで、私たちがする必要があるのはAWSのような環境変数としてAWS資格証明書を証明することです.S 3とAPIゲートウェイのような他のサービスを使用しているので、いくつかの変数が必要になります.
    GitHubアクションを使用してビルド、パッケージ、および配備を行います.では、Githubアクションを使ってCi/CDを実装する方法を見てみましょう.

    ギタブアクション
    GitHubアクションは、あなたのソフトウェア開発のライフサイクル内のタスクを自動化するのに役立ちます.Githubアクションはイベント駆動で、指定されたイベントが発生した後に一連のコマンドを実行することができます.たとえば、誰かがリポジトリのプル要求を作成するたびに、ソフトウェアテストスクリプトを実行するコマンドを自動的に実行できます.
    のような複数のトリガに基づいてgithubアクションを設定することができますpush , pull_request などと同様に別の枝ごとに.
    Serverless関数を展開する方法は2つあります.

  • 自動展開
  • トリガ設定
  •   name: Auto Serverless Deployment
      on: [push, pull_request]
    
  • 最初の仕事はretechファイルとフォルダーの変更をdetechにし、リストを返します.ここでは、現在と最後のコミットの間にgit commit diffを使用し、追加されたファイルや更新されたファイルリストを返すフィルタを使用します.その後、各ファイルを通過し、一意のフォルダ名だけを収集し、出力として返されます.
  •   jobs:
        changes:
          name: Changes
          runs-on: ubuntu-latest
          outputs:
            folders: ${{ steps.filter.outputs.folders }}
          steps:
            - uses: actions/checkout@v2
            - name: Check changed files
              id: diff
              run: |
                if [ $GITHUB_BASE_REF ]; then
                  # Pull Request
                  git fetch origin $GITHUB_BASE_REF --depth=1
                  export DIFF=$( git diff --name-only origin/$GITHUB_BASE_REF $GITHUB_SHA )
                  echo "Diff between origin/$GITHUB_BASE_REF and $GITHUB_SHA"
                else
                  # Push
                  git fetch origin ${{ github.event.before }} --depth=1
                  export DIFF=$( git diff --diff-filter=d --name-only ${{ github.event.before }} $GITHUB_SHA )
                  echo "Diff between ${{ github.event.before }} and $GITHUB_SHA"
                fi
                echo "$DIFF"
                # Escape newlines (replace \n with %0A)
                echo "::set-output name=diff::$( echo "$DIFF" | sed ':a;N;$!ba;s/\n/%0A/g' )"
            - name: Set matrix for build
              id: filter
              run: |
                DIFF="${{ steps.diff.outputs.diff }}"
    
                if [ -z "$DIFF" ]; then
                  echo "::set-output name=folders::[]"
                else
                  JSON="["
                  # Loop by lines
                  while read path; do
                    # Set $directory to substring before /
                    directory="$( echo $path | cut -d'/' -f1 -s )"
    
                  # ignore .github folder
                  if [[ "$directory" != ".github" ]]; then
                    # Add build to the matrix only if it is not already included
                    JSONline="\"$directory\","
                    if [[ "$JSON" != *"$JSONline"* ]]; then
                      JSON="$JSON$JSONline"
                    fi
                  fi
                  done <<< "$DIFF"
    
                  # Remove last "," and add closing brackets
                  if [[ $JSON == *, ]]; then
                    JSON="${JSON%?}"
                  fi
                  JSON="$JSON]"
                  echo $JSON
    
                  # Set output
                  echo "::set-output name=folders::$( echo "$JSON" )"
                fi
    
    Serverlessサービスの追加後servicea , ビルドアクションは以下のように表示されます.
    変更ファイルチェック
      Diff between 3da227687e19da14062916c6f71cef0c7e3f9033 and 96a8e3a39ab79ccff3a294ea485c4c3854d496c6
      servicea/.gitignore
      servicea/handler.js
      servicea/package-lock.json
      servicea/package.json
      servicea/serverless.yml
    
    ビルドの設定行列
      ["servicea"]
    
  • 次に、以下のような行列戦略を使って、フォルダ名ごとにジョブを作成します.
  •     deploy:
          needs: changes
          name: Deploy
          if: ${{ needs.changes.outputs.folders != '[]' && needs.changes.outputs.folders != '' }}
          strategy:
            matrix:
              # Parse JSON array containing names of all filters matching any of changed files
              # e.g. ['servicea', 'serviceb'] if both package folders contains changes
              folder: ${{ fromJSON(needs.changes.outputs.folders) }}
    
  • さあ、関数をビルドして配備する時です.Serverlessで使用されるすべてのENV変数を定義してください.以下のようにENVセクションのYMLファイル.だからここでは、すべてのフォルダと実行を通過しますnpx serverless deploy . このコマンドはZIPを作成し、S 3に更新し、ラムダを作成/更新し、最終的にAPIゲートウェイで設定します.
  •       runs-on: ubuntu-latest
          steps:
            - uses: actions/checkout@v2
            - name: Configure AWS Credentials
              uses: aws-actions/configure-aws-credentials@v1
              with:
                aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
                aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
                aws-region: ${{ secrets.AWS_REGION }}
            - name: deploy
              run: npx serverless deploy
              working-directory: ${{ matrix.folder }}
              env:
                AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
                AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
                AWS_REST_API_ROOT_ID: ${{ secrets.AWS_REST_API_ROOT_ID }}
                AWS_REST_API_ID: ${{ secrets.AWS_REST_API_ID }}
                AWS_BUCKET_NAME: ${{ secrets.AWS_BUCKET_NAME }}
    
    コードを開始し、コードを展開して展開する前に、秘密の環境変数を追加する必要があります.rootアカウントを使用してはならず、ユースケースに基づいて制限されたパーミッションを持つ新しいユーザーを常に作成します.このプロセスでは、ラムダ、S 3書き込みアクセス、APIゲートウェイへのアクセスが必要です.
      - AWS_ACCESS_KEY_ID
      - AWS_SECRET_ACCESS_KEY
      - AWS_REGION
      - AWS_REST_API_ROOT_ID
      - AWS_REST_API_ID
      - AWS_BUCKET_NAME: bucket name where we want our zip files to be stored, if you are ignoring `deploymentBucket` from `serverless.yml` file, you can ignore this variable as well.
    

    オート.気象研
    name: Auto Serverless Deployment
    on: [push, pull_request]
    
    jobs:
      changes:
        name: Changes
        runs-on: ubuntu-latest
        outputs:
          folders: ${{ steps.filter.outputs.folders }}
        steps:
          - uses: actions/checkout@v2
          - name: Check changed files
            id: diff
            run: |
              if [ $GITHUB_BASE_REF ]; then
                # Pull Request
                git fetch origin $GITHUB_BASE_REF --depth=1
                export DIFF=$( git diff --name-only origin/$GITHUB_BASE_REF $GITHUB_SHA )
                echo "Diff between origin/$GITHUB_BASE_REF and $GITHUB_SHA"
              else
                # Push
                git fetch origin ${{ github.event.before }} --depth=1
                export DIFF=$( git diff --diff-filter=d --name-only ${{ github.event.before }} $GITHUB_SHA )
                echo "Diff between ${{ github.event.before }} and $GITHUB_SHA"
              fi
              echo "$DIFF"
              # Escape newlines (replace \n with %0A)
              echo "::set-output name=diff::$( echo "$DIFF" | sed ':a;N;$!ba;s/\n/%0A/g' )"
          - name: Set matrix for build
            id: filter
            run: |
              DIFF="${{ steps.diff.outputs.diff }}"
    
              if [ -z "$DIFF" ]; then
                echo "::set-output name=folders::[]"
              else
                JSON="["
                # Loop by lines
                while read path; do
                  # Set $directory to substring before /
                  directory="$( echo $path | cut -d'/' -f1 -s )"
    
                # ignore .github folder
                if [[ "$directory" != ".github" ]]; then
                  # Add build to the matrix only if it is not already included
                  JSONline="\"$directory\","
                  if [[ "$JSON" != *"$JSONline"* ]]; then
                    JSON="$JSON$JSONline"
                  fi
                fi
                done <<< "$DIFF"
    
                # Remove last "," and add closing brackets
                if [[ $JSON == *, ]]; then
                  JSON="${JSON%?}"
                fi
                JSON="$JSON]"
                echo $JSON
    
                # Set output
                echo "::set-output name=folders::$( echo "$JSON" )"
              fi
      deploy:
        needs: changes
        name: Deploy
        if: ${{ needs.changes.outputs.folders != '[]' && needs.changes.outputs.folders != '' }}
        strategy:
          matrix:
            # Parse JSON array containing names of all filters matching any of changed files
            # e.g. ['servicea', 'serviceb'] if both package folders contains changes
            folder: ${{ fromJSON(needs.changes.outputs.folders) }}
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v2
          - name: Configure AWS Credentials
            uses: aws-actions/configure-aws-credentials@v1
            with:
              aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
              aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
              aws-region: ${{ secrets.AWS_REGION }}
          - name: deploy
            run: npx serverless deploy
            working-directory: ${{ matrix.folder }}
            env:
              AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
              AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
              AWS_REST_API_ROOT_ID: ${{ secrets.AWS_REST_API_ROOT_ID }}
              AWS_REST_API_ID: ${{ secrets.AWS_REST_API_ID }}
              AWS_BUCKET_NAME: ${{ secrets.AWS_BUCKET_NAME }}
    

  • マニュアル
  • 時々、手動で関数を展開したり、上記のスクリプトでスキップすることもできます.
    ここでは、git diffを使ってファイルを特定し、フォルダを返す手順を飛ばします.機能フォルダ名の中に直接展開することができます.npx serverless deploy .

  • 手動展開では、関数名をアクション入力として手動で展開するのではなく、特定の関数を展開します.
      name: Manual Serverless Deployment
      on:
        push:
          branches:
            - main
        pull_request:
          branches:
            - main
        workflow_dispatch:
          inputs:
            function:
              description: "Function name"
              required: true
    

  • この後、我々は以下のように我々の仕事でそれを使用します.
      jobs:
        deploy:
          if: ${{ github.event_name == 'workflow_dispatch' }}
          name: deploy
          runs-on: ubuntu-latest
          steps:
            - uses: actions/checkout@master
            - name: deploy
              run: npx serverless deploy
              working-directory: ${{ github.event.inputs.function }}
              env:
                AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
                AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
                AWS_REST_API_ROOT_ID: ${{ secrets.AWS_REST_API_ROOT_ID }}
                AWS_REST_API_ID: ${{ secrets.AWS_REST_API_ID }}
                AWS_BUCKET_NAME: ${{ secrets.AWS_BUCKET_NAME }}
    

  • マニュアル.気象研
    name: Manual Serverless Deployment
    on:
      push:
        branches:
          - main
      pull_request:
        branches:
          - main
      workflow_dispatch:
        inputs:
          function:
            description: "Function name"
            required: true
    jobs:
      deploy:
        if: ${{ github.event_name == 'workflow_dispatch' }}
        name: deploy
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@master
          - name: deploy
            run: npx serverless deploy
            working-directory: ${{ github.event.inputs.function }}
            env:
              AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
              AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
              AWS_REST_API_ROOT_ID: ${{ secrets.AWS_REST_API_ROOT_ID }}
              AWS_REST_API_ID: ${{ secrets.AWS_REST_API_ID }}
              AWS_BUCKET_NAME: ${{ secrets.AWS_BUCKET_NAME }}
    
    
    ここでは、私たちはどのようにセットアップを自動設定することができます手動でのCGI/CDとサーバーレスアプリケーションの配備.

    リンク
  • Serverless Framework
  • CI/CD for monorepos