ちょっと話題の記事

「IaCで全てが上手くいくと思うなよ!失敗事例のご紹介」をAKIBA.AWS IaC編で喋りました #AKIBAAWS

2021.07.07

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

どうも、もこ@札幌オフィスです。

5/18に開催されたAKIBA.AWS ONLINE #03 -「IaCで全てが上手くいくと思うなよ!失敗事例のご紹介」のタイトルで登壇しましたので、資料と補足情報を公開します。

なお、YouTubeで公開されている動画ではより本質的な情報をノーカットで公開していますので、お時間のある方はこちらを見て頂いた方が情報量が多くて良いかと思います。

本記事ではスライドに合わせてブログ形式で解説を付属していきます。

結論

早速結論です。

  • IaCは手段であり、目的ではない、が、目標にするのは良い事
  • 何も考えないでIaCを始めると破綻する
  • リソースを素手で触らない強い意思

この3つが非常に重要になってきます。

本日お話しすることです。

1. IaCについてのおさらい

今回はOSレイヤーやミドルレイヤーでのIaCについては触れません。理由は後述しますが、簡単に言うと「AWS環境とIaCとOS/ミドルレイヤーのIaCは別で考えるべき」だと思うからです。

AWSのIaCでは主に Terraform / CloudFormation / CDK / AWS SAM / Serverless Frameworkなどを利用しているケースが大半だと思います。

Terraformの場合、VPC1つとSubnetを作成するためには次のように記述することが出来ます。

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  instance_tenancy     = "default"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "main-vpc"
  }
}

resource "aws_subnet" "main_subnet_a" {
  vpc_id     = aws_vpc.vpc.id
  cidr_block = "10.0.0.0/24"
  availability_zone = "ap-northeast-1a"
  tags = {
    Name = "main-subnet-a"
  }
}

CloudFormationの場合は次のように記載できます。

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  MainVpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: "10.0.0.0/16"
      Tags:
        - Key: Name
          Value: "main-vpc" 
  MainSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MainVpc
      AvailabilityZone: ap-northeast-1a
      CidrBlock: "10.0.0.0/24"
      Tags:
        - Key: Name
          Value: "main-subnet-a"

CDKの場合は、次のように記載することで、下記の構成図のような構成をまるっと作る事が出来ます。

CDKはTypeScriptなどの使い慣れたプログラミング言語で記述することができ、サーバーレス開発の強い味方なため、最近はCDKを利用するのが主流な気がします。

import * as cdk from '@aws-cdk/core';
import { Vpc, SubnetType } from '@aws-cdk/aws-ec2';

export class VpcSubnetSampleStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    new Vpc(this, 'Vpc', {
      cidr: '10.0.0.0/16',
      enableDnsHostnames: true,
      enableDnsSupport: true,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: 'PublicSubnet',
          subnetType: SubnetType.PUBLIC,
        },
      ],
      maxAzs: 2,
    });
  }
}

その他にもCloudFormationの拡張機能とCLIを提供しているAWS SAMや、Serverless Frameworkといったものもあります。

2. あるある失敗例3パターン

早速本題です、あるある失敗例3パターンをご紹介します。

プロジェクトメンバーがクラウド + IaC未経験で学習コストが発生するパターン

IaCツールの導入にはIaCツール固有の挙動を把握したり、そもそものクラウドの正確な仕様を理解する必要があります。

そのため、IaCツールの導入の際には少なからず学習コストが発生します。この学習コストを払い続けて運用も進める事ができる場合は良いのですが、途中で諦めたり、めんどくさくなって投げ出すと、後述する「手動オペレーションで破綻するパターン」の失敗に繋がります。

IaC化が目的になっているパターン

IaC化が目的となってしまっているパターンもあるある失敗例です。手動で構築したり運用したりするのを完全な悪と捉えて、明らかに手動でやった方が良いケースでも無理にIaCを利用しようとすると、無駄な運用コストが発生してしまいます。例えば、あまり良くない例ではありますが、今後2-3年塩漬けするようなシステムで頻繁な構成変更を行わない場合、運用でIaCを利用するメリットはあまりありません。

そのため、「運用では手動オペレーションだが、構築はIaCで行う」など事前にどのフェーズまでIaCを活用するのかは要検討かと思います。

手動オペレーションで破綻するパターン

これが最もあるあるだと思います。

IaCで管理して、運用をしているリソースをマネコンから手動で変更した場合、「事前にレビューされた構成変更を行える」IaCのメリットも消えてしまいますし、コードと環境との差分が発生してしまいますので、IaCのメリットを全て殺してしまう最悪なケースです。

この「手動オペレーション」をやってしまう要因は様々ありますが、一度IaCで運用することを決めた場合は絶対に環境を素手で触らない強い意志が必要ですし、どうしても手動オペレーションが必要なケースにのみ編集権限を付与するような権限管理をするべきです。

また、IaCで運用と構成管理は出来ているが、IaCのデプロイは手動で行っている場合もあると思います。これはケースバイケースだとは思いますが一番良くないかと思います。

ソースコードはGitHub等で管理された上でmainブランチにMergeされたら自動で環境に適用するような仕組みにするべきですし、レビューが通っていない構成変更は行われないべきです。

もっと言うならば、クラウド環境に書き込みが出来るAdmin的な権限を持つべきではありません。IaCのCI/CD環境にだけ適切な書き込み権限を持つIAMを払い出した上で、個々の開発者はReadOnly権限を割り振り、本当に必要なケースにのみAdmin権限を貰えるような仕組みを作るのが最適です。

IaCのCI/CD環境にだけ書き込み権限(Adminやもっと絞った権限)を持たせた上でBranch Protectionを行うだけで環境編集のコード的な証跡も残り、権限を持つ従業員による不正・オペミスも最小限にする事ができます。

とある事故CloudFormationリポジトリ

とある事故CloudFormationリポジトリを紹介します。

前提として、CloudFormationではスタック(ファイル)を分割することができ、スタック間のパラメーターはOutputsという仕組みで値を出力した上で、ImportValueを行う事で、子スタックが親スタックの情報を見てリソースを作成する事ができます。

そのため、network、ec2、albと3つのスタックを別けてみました。スライドではOutputsは赤文字、ImportValue(参照)は緑色の文字で表記しています。

これでもパッと見でよさそうですが、ここで「ダウンタイムなしでEC2を入れ替えたい」となった場合、どうすれば良いのでしょうか。

このように、4回スタックを更新する必要があり、非常につらいです。

どうしてこうなってしまったのかを振り返ってみると、依存関係がごっちゃりしてしまったのが原因であることが浮き彫りになります。

本来はCloudFormationはライフサイクルごとに管理するべきで、EC2とALBは同じスタックにすると楽になれました。今回はEC2のYAMLだけでも数百行行ってしまっていたため、ひよってALBとファイルを別けてしまい、失敗してしまいました。

NestedStackを使うとそこらへんもまるっと更新する事も出来ますが、子Stackの差分が見れるようになったのが最近だったり、諸々のツラミがたくさんあるため、CloudFormationの依存関係問題はかなり難しいなと思います。

また、こういったCloudFormationのデプロイは手動で依存関係を把握した上で1個1個、「ぬくもりのある」デプロイを行う必要がありますので、そこでオペミスが発生すると最高にめんどくさい事になりますし、精神もかなり消耗します。

そのため、ある程度規模が大きい場合はTerraformの導入をお勧めします。ファイルを分割しても全ての差分を見て良い感じに順序立てて構成変更をしてくれます。

改めて。IaCのメリット、デメリット

ここまでIaCの事をDisってきているように思われたかもしれませんが、IaCが無いと苦労する事もたくさんありますし、IaCは最高です。

具体的には次のようなメリットを受けれます。

が、これらは正しくIaCを使った時のみ受けれる恩恵となります。

逆にデメリットを上げると、時間がかかったり、設計をきちんと考える必要があったり、IaCの運用を投げ出してしまうほうが実は幸せになれる場合もあったりなかったりします。

3. オレ流IaCベストプラクティス

そこで、今日は実際に私が実際に2年間、数十プロジェクトで経験して培ってきたオレ流IaCベストプラクティスをご紹介します。

IaCとの付き合い方を先に決める

IaCを使った運用をする場合、予め運用を見据えた設計をする必要がありますが、構築だけで利用する場合はサクサク使えます。

構築だけで使い捨てる場合は何も考えなくてOKですが、運用もしっかりする場合はしっかり考える必要がありますが、逆に言ってしまえば初期設計だけきちんとすれば割と後からでも軌道修正をできます。

IaCもCI/CDする

IaCも通常のアプリケーションのようにGitを使って管理して、Pull Requestでレビューをして、Mergeされたら自動でデプロイが走るようにしましょう。

これはとあるGitHubをフル活用しているプロジェクトの例ですが、このように、レビューした上でApproveしてMerge、自動でデプロイまでの流れを作れると最適です。

Terraformの場合はTerraform Cloudを使うのもアリです。S3 Backendで使うのもアリですが、プロジェクトの規模が大きくなってきたり、GitHub Actionsなどの設定がめんどくさい場合は、Terraform Cloudがサクサクと使えて良い感じです。

AWS環境は素手で触らない

IaCで運用している場合、AWS環境は絶対に素手で触らないでください。

これは前述しましたが、IaCのバグやどうしても手動オペレーションが必要な時にのみ手動で触るようにしましょう。通常のオペレーションで利用してしまうとIaCを破綻させてしまい、全ての努力が水の泡、IaCのメリットを全て殺してしまう最悪な行為となってしまいます。

普段使いの権限はReadOnly権限にして、本当に必要な時にのみAdmin権限を使うようなフローにして、権限的にも物理的に触れない環境に出来ればベストです。

スケーリングさせる

IaCをする前にアプリケーションをステートレスにしてスケーリングさせるタスクの方が優先度が高い場合が多いかと思います。せっかくクラウド使うならガンガンスケーリングさせて行きましょう!

アプリのデプロイとIaCは別で考える

CDKやSAMの場合は一緒の方が良いですが、TerraformやCloudFormationの場合はアプリのデプロイとIaCは別で考えた方が楽です。また、ミドルレイヤーについては意識しなくても良い(最小限意識するレベルで活用できるコンテナ環境などの)状態にすると良いです。ミドルレイヤーはIaCで極力意識しないようにしましょう。

まとめ

IaCのメリットは大きいので、ガンガン使って行きましょう。ただし、ある程度注意するべきポイントもあるので、このスライド(記事)を参考にして回避して頂けると嬉しいです。また、せっかくAWSを使うならよりよい環境にしていきましょう。IaC化も重要ですが、個人的にはAurora化だったりステートレスにしてAutoScaling対応をしたりする方が優先度が高いと思いますので、優先度を考えて実践して頂けると良いかと思います。

資料