AWSでのTerraformによるダウンタイムゼロのJenkins継続的デプロイ

公開: 2022-03-11

文字通りすべてが24時間年中無休で稼働する必要がある今日のインターネットの世界では、信頼性が鍵となります。 これは、最新リリースのロールアウト中に、Webサイトのダウンタイムがほぼゼロになり、恐ろしい「見つかりません:404」エラーページやその他のサービスの中断を回避することを意味します。

クライアントまたはおそらく自分自身のために新しいアプリケーションを構築し、そのアプリケーションを気に入った優れたユーザーベースを獲得できたとします。 ユーザーからフィードバックを収集し、開発者のところに行き、新しい機能を構築してアプリケーションをデプロイできるようにするように依頼します。 これで、アプリケーション全体を停止して新しいバージョンをデプロイするか、ダウンタイムゼロのCI / CDデプロイメントパイプラインを構築して、手動で介入することなく新しいリリースをユーザーにプッシュするという面倒な作業をすべて行うことができます。

この記事では、後者について正確に説明します。Terraformをインフラストラクチャオーケストレーターとして使用して、AWSクラウド上のNode.jsに構築された3層Webアプリケーションの継続的デプロイパイプラインを作成する方法について説明します。 継続的デプロイの部分にはJenkinsを使用し、コードベースのホストにはBitbucketを使用します。

コードリポジトリ

ここでコードを見つけることができるデモの3層Webアプリケーションを使用します。

リポジトリには、WebレイヤーとAPIレイヤーの両方のコードが含まれています。 これは、WebモジュールがAPIレイヤーのエンドポイントの1つを呼び出し、データベースから現在の時刻に関する情報を内部的にフェッチしてWebレイヤーに戻る単純なアプリケーションです。

リポジトリの構造は次のとおりです。

  • API: APIレイヤーのコード
  • Web: Webレイヤーのコード
  • Terraform:Terraformを使用したインフラストラクチャオーケストレーションのコード
  • Jenkins: CI/CDパイプラインに使用されるJenkinsサーバーのインフラストラクチャオーケストレーターのコード。

デプロイする必要があるものがわかったので、このアプリケーションをAWSにデプロイするために必要なことについて説明し、次にCI/CDパイプラインのその部分を作成する方法について説明します。

画像を焼く

インフラストラクチャオーケストレーターにTerraformを使用しているため、デプロイする層またはアプリケーションごとにプリベークされたイメージを用意するのが最も理にかなっています。 そのためには、Hashicorpの別の製品であるPackerを使用します。

Packerは、AWSでのデプロイに使用されるAmazonMachineImageまたはAMIの構築に役立つオープンソースツールです。 EC2、VirtualBox、VMwareなどのさまざまなプラットフォーム用のイメージを構築するために使用できます。

これは、Packer構成ファイル( terraform/packer-ami-api.json )を使用してAPIレイヤーのAMIを作成する方法のスニペットです。

 { "builders": [{ "type": "amazon-ebs", "region": "eu-west-1", "source_ami": "ami-844e0bf7", "instance_type": "t2.micro", "ssh_username": "ubuntu", "ami_name": "api-instance {{timestamp}}" }], "provisioners": [ { "type": "shell", "inline": ["mkdir api", "sudo apt-get update", "sudo apt-get -y install npm nodejs-legacy"], "pause_before": "10s" }, { "type": "file", "source" : "../api/", "destination" : "api" }, { "type": "shell", "inline": ["cd api", "npm install"], "pause_before": "10s" } ] }

また、AMIを作成するには、次のコマンドを実行する必要があります。

 packer build -machine-readable packer-ami-api.json

このコマンドは、この記事の後半でJenkinsビルドから実行します。 同様の方法で、WebレイヤーにもPacker構成ファイル( terraform/packer-ami-web.json )を使用します。

上記のPacker構成ファイルを調べて、何をしようとしているのかを理解しましょう。

  1. 前述のように、Packerは多くのプラットフォームのイメージを構築するために使用できます。また、アプリケーションをAWSにデプロイしているため、ビルダー「amazon-ebs」を使用します。これは、最も簡単に開始できるビルダーです。
  2. 構成の2番目の部分は、イメージの構成に使用できるスクリプトまたはコードブロックに似たプロビジョナーのリストを取得します。
    • ステップ1は、シェルプロビジョナーを実行してAPIフォルダーを作成し、実行するコマンドのセットであるinlineプロパティを使用してイメージにNode.jsをインストールします。
    • ステップ2では、ファイルプロビジョナーを実行して、ソースコードをAPIフォルダーからインスタンスにコピーします。
    • 手順3でもシェルプロビジョナーを実行しますが、今回はscriptプロパティを使用して、実行する必要のあるコマンドを含むファイル(terraform / scripts / install_api_software.sh)を指定します。
    • ステップ4は、次のステップでインストールされるCloudwatchに必要なインスタンスに設定ファイルをコピーします。
    • ステップ5では、シェルプロビジョナーを実行してAWSCloudwatchエージェントをインストールします。 このコマンドへの入力は、前の手順でコピーされた構成ファイルになります。 Cloudwatchについては、この記事の後半で詳しく説明します。

したがって、基本的に、Packer構成には、必要なビルダーに関する情報と、イメージの構成方法に応じて任意の順序で定義できる一連のプロビジョナーが含まれます。

Jenkins継続的デプロイのセットアップ

次に、CI/CDパイプラインに使用されるJenkinsサーバーのセットアップについて検討します。 これもセットアップにTerraformとAWSを使用します。

Jenkinsを設定するためのTerraformコードは、 jenkins/setupフォルダー内にあります。 この設定に関する興味深い点をいくつか見ていきましょう。

  1. AWSクレデンシャル: AWSアクセスキーIDとシークレットアクセスキーをTerraform AWSプロバイダー( instance.tf )に提供するか、AWSプロバイダーのプロパティshared_credentials_fileにクレデンシャルファイルの場所を指定することができます。
  2. IAMの役割: JenkinsサーバーからPackerとTerraformを実行するため、AWSのS3、EC2、RDS、IAM、負荷分散、自動スケーリングサービスにアクセスします。 したがって、これらのサービスにアクセスするためにJenkins for Packer&Terraformにクレデンシャルを提供するか、IAMプロファイル( iam.tf )を作成して、これを使用してJenkinsインスタンスを作成します。
  3. Terraformの状態: Terraformは、ファイル内のどこかにインフラストラクチャの状態を維持する必要があります。S3( backend.tf )を使用すると、インフラストラクチャの状態をそこに維持できるため、他の同僚と共同作業を行うことができ、状態以降、誰でも変更およびデプロイできます。離れた場所に維持されます。
  4. 公開鍵と秘密鍵のペア:キーペアの公開鍵をインスタンスと一緒にアップロードして、起動したJenkinsインスタンスにSSHで接続できるようにする必要があります。 Terraform変数を使用して公開鍵の場所を指定するaws_key_pairリソース( key.tf )を定義しました。

Jenkinsを設定する手順:

ステップ1: Terraformのリモート状態を維持するには、Terraformで使用できるバケットをS3で手動で作成する必要があります。 これは、Terraformの外部で実行される唯一のステップです。 以下のコマンドを実行してAWSクレデンシャルを指定する前に、必ずAWS configureを実行してください。

 aws s3api create-bucket --bucket node-aws-jenkins-terraform --region eu-west-1 --create-bucket-configuration LocationConstraint=eu-west-1

ステップ2: terraform initを実行します。 これにより、状態が初期化され、S3に保存されるように設定され、AWSプロバイダープラグインがダウンロードされます。

ステップ3: terraformapplyを実行terraform apply 。 これにより、すべてのTerraformコードがチェックされ、計画が作成され、このステップの終了後に作成されるリソースの数が示されます。

ステップ4: yesと入力すると、前のステップですべてのリソースの作成が開始されます。 コマンドが終了すると、JenkinsサーバーのパブリックIPアドレスを取得します。

ステップ5:秘密鍵を使用してJenkinsサーバーにSSH接続します。 ubuntuは、AWSEBSがサポートするインスタンスのデフォルトのユーザー名です。 terraform applyコマンドによって返されたIPアドレスを使用します。

 ssh -i mykey [email protected]

ステップ6: http://34.245.4.73:8080にアクセスして、 http://34.245.4.73:8080を開始します。 パスワードは/var/lib/jenkins/secrets/initialAdminPasswordます。

ステップ7: 「推奨プラグインのインストール」を選択し、Jenkinsの管理者ユーザーを作成します。

JenkinsとBitbucket間のCIパイプラインの設定

  1. このためには、JenkinsにBitbucketプラグインをインストールする必要があります。 [Jenkinsの管理]→[プラグインの管理]に移動し、[利用可能なプラグイン]からBitbucketプラグインをインストールします。
  2. Bitbucketリポジトリ側で、 [設定]→[Webhook]に移動し、新しいWebhookを追加します。 このフックは、リポジトリ内のすべての変更をJenkinsに送信し、パイプラインをトリガーします。
    Bitbuckerを介したJenkins継続的デプロイへのWebhookの追加

画像をベイク/ビルドするためのJenkinsパイプライン

  1. 次のステップは、Jenkinsでパイプラインを作成することです。
  2. 最初のパイプラインは、Packerを使用してアプリケーションのAMIを構築するために使用されるFreestyleプロジェクトになります。
  3. BitbucketリポジトリのクレデンシャルとURLを指定する必要があります。
    bitbucketにクレデンシャルを追加する
  4. ビルドトリガーを指定します。
    ビルドトリガーの構成
  5. 2つのビルドステップを追加します。1つはアプリモジュール用のAMIをビルドするためのもので、もう1つはWebモジュール用のAMIをビルドするためのものです。
    AMIビルドステップの追加
  6. これが完了すると、Jenkinsプロジェクトを保存できます。これで、Bitbucketリポジトリに何かをプッシュすると、Jenkinsで新しいビルドがトリガーされ、AMIが作成され、そのイメージのAMI番号を含むTerraformファイルがビルドステップの最後の2行からわかるS3バケット。
 echo 'variable "WEB_INSTANCE_AMI" { default = "'${AMI_ID_WEB}'" }' > amivar_web.tf aws s3 cp amivar_web.tf s3://node-aws-jenkins-terraform/amivar_web.tf

TerraformスクリプトをトリガーするJenkinsパイプライン

APIとWebモジュールのAMIができたので、ビルドをトリガーして、アプリケーション全体をセットアップするためのTerraformコードを実行し、後でこのパイプラインにサービスのダウンタイムなしで変更をデプロイさせるTerraformコードのコンポーネントを調べます。

  1. 別のフリースタイルのJenkinsプロジェクトであるnodejs-terraformを作成します。このプロジェクトは、Terraformコードを実行してアプリケーションをデプロイします。
  2. まず、グローバルクレデンシャルドメインに「シークレットテキスト」タイプのクレデンシャルを作成します。これは、Terraformスクリプトへの入力として使用されます。 TerraformとGit内でRDSサービスのパスワードをハードコーディングしたくないので、Jenkinsクレデンシャルを使用してそのプロパティを渡します。
    Terraformcicdで使用するためのシークレットを作成する
  3. 他のプロジェクトと同様に、資格情報とURLを定義する必要があります。
  4. ビルドトリガーセクションでは、前のプロジェクトが終了したときにこのプロジェクトが開始されるように、このプロジェクトを他のプロジェクトとリンクします。
    プロジェクトをリンクする
  5. 次に、バインディングを使用してプロジェクトに以前に追加した資格情報を構成できるため、ビルドステップで使用できます。
    バインディングの構成
  6. これで、ビルドステップを追加する準備が整いました。これにより、前のプロジェクトによってS3にアップロードされたTerraformスクリプトファイル( amivar_api.tfおよびamivar_web.tf )がダウンロードされ、Terraformコードを実行してAWSでアプリケーション全体がビルドされます。
    ビルドスクリプトの追加

すべてが適切に構成されている場合、コードをBitbucketリポジトリにプッシュすると、最初のJenkinsプロジェクトがトリガーされ、次に2番目のプロジェクトがトリガーされ、アプリケーションがAWSにデプロイされます。

AWS用のTerraformゼロダウンタイム構成

次に、このパイプラインがダウンタイムなしでコードをデプロイできるようにするTerraformコードの内容について説明します。

まず、Terraformは、オプションcreate_before_destroyをフラグとして持つリソースに対して、これらのライフサイクル構成ブロックを提供します。これは、文字通り、Terraformが現在のリソースを破棄する前に同じタイプの新しいリソースを作成する必要があることを意味します。

ここで、 aws_autoscaling_groupおよびaws_launch_configurationリソースでこの機能を活用します。 そのため、 aws_launch_configurationは、プロビジョニングするEC2インスタンスのタイプと、そのインスタンスにソフトウェアをインストールする方法を設定し、 aws_autoscaling_groupリソースはAWS自動スケーリンググループを提供します。

ここで興味深いのは、Terraformのすべてのリソースに一意の名前とタイプの組み合わせが必要なことです。 したがって、新しいaws_autoscaling_groupaws_launch_configurationに別の名前を付けない限り、現在のaws_autoscaling_groupを破棄することはできません。

Terraformは、 aws_launch_configurationリソースにname_prefixプロパティを提供することにより、この制約を処理します。 このプロパティが定義されると、Terraformはすべてのaws_launch_configurationリソースに一意のサフィックスを追加し、その一意の名前を使用してaws_autoscaling_groupリソースを作成できます。

上記のすべてのコードは、 terraform/autoscaling-api.tfで確認できます。

 resource "aws_launch_configuration" "api-launchconfig" { name_prefix = "api-launchconfig-" image_ instance_type = "t2.micro" security_groups = ["${aws_security_group.api-instance.id}"] user_data = "${data.template_file.api-shell-script.rendered}" iam_instance_profile = "${aws_iam_instance_profile.CloudWatchAgentServerRole-instanceprofile.name}" connection { user = "${var.INSTANCE_USERNAME}" private_key = "${file("${var.PATH_TO_PRIVATE_KEY}")}" } lifecycle { create_before_destroy = true } } resource "aws_autoscaling_group" "api-autoscaling" { name = "${aws_launch_configuration.api-launchconfig.name}-asg" vpc_zone_identifier = ["${aws_subnet.main-public-1.id}"] launch_configuration = "${aws_launch_configuration.api-launchconfig.name}" min_size = 2 max_size = 2 health_check_grace_period = 300 health_check_type = "ELB" load_balancers = ["${aws_elb.api-elb.name}"] force_delete = true lifecycle { create_before_destroy = true } tag { key = "Name" value = "api ec2 instance" propagate_at_launch = true } }

また、ダウンタイムのないデプロイメントでの2番目の課題は、新しいデプロイメントがリクエストの受信を開始する準備ができていることを確認することです。 状況によっては、新しいEC2インスタンスをデプロイして起動するだけでは不十分です。

この問題を解決するために、 aws_launch_configurationには、ネイティブAWS自動スケーリングuser_dataプロパティをサポートするプロパティuser_dataがあります。これを使用して、自動スケーリンググループの一部として新しいインスタンスの起動時に実行するスクリプトを渡すことができます。 この例では、アプリサーバーのログを追跡し、起動メッセージが表示されるのを待ちます。 HTTPサーバーをチェックして、いつ稼働しているかを確認することもできます。

 until tail /var/log/syslog | grep 'node ./bin/www' > /dev/null; do sleep 5; done

それに加えて、 aws_autoscaling_groupリソースレベルでELBチェックを有効にすることもできます。これにより、Terraformが古いインスタンスを破棄する前に、新しいインスタンスが追加されてELBチェックに合格することが確認されます。 これは、APIレイヤーのELBチェックがどのように見えるかです。 /api/statusエンドポイントをチェックして成功を返します。

 resource "aws_elb" "api-elb" { name = "api-elb" subnets = ["${aws_subnet.main-public-1.id}"] security_groups = ["${aws_security_group.elb-securitygroup.id}"] listener { instance_port = "${var.API_PORT}" instance_protocol = "http" lb_port = 80 lb_protocol = "http" } health_check { healthy_threshold = 2 unhealthy_threshold = 2 timeout = 3 target = "HTTP:${var.API_PORT}/api/status" interval = 30 } cross_zone_load_balancing = true connection_draining = true connection_draining_timeout = 400 tags { Name = "my-elb" } }

まとめと次のステップ

これで、この記事の終わりになります。 うまくいけば、これまでに、アプリケーションをデプロイして、JenkinsデプロイメントとTerraformのベストプラクティスを使用してダウンタイムゼロのCI / CDパイプラインで実行しているか、この領域を探索してデプロイメントを手動で行う必要がないほど快適になっていることを願っています。可能。

この記事では、使用されている展開戦略をBlue-Green展開と呼びます。現在のインストール(Blue)は、新しいバージョン(Green)の展開とテスト中にライブトラフィックを受信し、新しいバージョンがすべての準備ができました。 この戦略とは別に、アプリケーションをデプロイする方法は他にもあります。これについては、この記事「デプロイメント戦略の概要」で詳しく説明しています。 別の戦略の適応は、Jenkinsパイプラインを構成するのと同じくらい簡単になりました。

また、この記事では、API、Web、およびデータレイヤーのすべての新しい変更に互換性があるため、新しいバージョンが古いバージョンと通信することを心配する必要はないと想定しました。 しかし、実際には、常にそうであるとは限りません。 この問題を解決するには、新しいリリース/機能を設計する際に、常に下位互換性レイヤーについて考えてください。そうしないと、その状況を処理するためにデプロイメントを微調整する必要があります。

統合テストは、このデプロイメントパイプラインにも欠けているものです。 テストせずにエンドユーザーに何もリリースしたくないので、これらの戦略を自分のプロジェクトに適用するときは、間違いなく覚えておく必要があります。

Terraformの仕組みと、テクノロジーを使用してAWSにデプロイする方法について詳しく知りたい場合は、 Terraform AWS Cloud:Sane Infrastructure Managementをお勧めします。ここでは、仲間のToptaler Radoslaw SzalskiがTerraformについて説明し、マルチを構成するために必要な手順を示します。 -チーム向けの環境と本番環境に対応したTerraformのセットアップ

関連: TerraformとCloudFormation:決定的なガイド