web-dev-qa-db-ja.com

AWS Cloudformation:EC2を作成するときにuser-dataパラメーターに配置されたbashスクリプトを再利用するにはどうすればよいですか?

Cloudformationには、2つのスタックがあります(1つはネストされています)。

ネストされたスタック「ec2-setup」:

{
  "AWSTemplateFormatVersion" : "2010-09-09",

  "Parameters" : {
    // (...) some parameters here

    "userData" : {
      "Description" : "user data to be passed to instance",
      "Type" : "String",
      "Default": ""
    }

  },

  "Resources" : {

    "EC2Instance" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "UserData" : { "Ref" : "userData" },
        // (...) some other properties here
       }
    }

  },
  // (...)
}

ここで、メインテンプレートで、上記のネストされたテンプレートを参照し、userDataパラメーターを使用してbashスクリプトを渡します。さらにユーザーデータスクリプトのコンテンツをインライン化したくないいくつかのec2インスタンスで再利用したいので(メインテンプレートでec2インスタンスを宣言するたびにスクリプトを複製したくない) )。

スクリプトの内容をパラメーターのデフォルト値として設定することで、これを実現しようとしました。

{
  "AWSTemplateFormatVersion": "2010-09-09",

  "Parameters" : {
    "myUserData": {
      "Type": "String",
      "Default" : { "Fn::Base64" : { "Fn::Join" : ["", [
        "#!/bin/bash \n",
        "yum update -y \n",

        "# Install the files and packages from the metadata\n",
        "echo 'tralala' > /tmp/hahaha"
      ]]}}
    }
  },
(...)

    "myEc2": {
      "Type": "AWS::CloudFormation::Stack",
      "Properties": {
        "TemplateURL": "s3://path/to/ec2-setup.json",
        "TimeoutInMinutes": "10",
        "Parameters": {
          // (...)
          "userData" : { "Ref" : "myUserData" }
        }

しかし、スタックを起動しようとすると、次のエラーが発生します。

「テンプレート検証エラー:テンプレートフォーマットエラー:すべてのデフォルトメンバーは文字列である必要があります。」

このエラーは、宣言{Fn :: Base64(...)}がオブジェクトであり、文字列ではないという事実が原因のようです(ただし、base64でエンコードされた文字列が返されます)。

ネストされたテンプレートを呼び出すときに(パラメーターとして設定された文字列を参照する代わりに)スクリプトをパラメーターセクションに直接(インラインスクリプトとして)貼り付けると、すべて正常に機能します。

"myEc2": {
  "Type": "AWS::CloudFormation::Stack",
  "Properties": {
    "TemplateURL": "s3://path/to/ec2-setup.json",
    "TimeoutInMinutes": "10",
    "Parameters": {
      // (...)
      "userData" : { "Fn::Base64" : { "Fn::Join" : ["", [
        "#!/bin/bash \n",
        "yum update -y \n",

        "# Install the files and packages from the metadata\n",
        "echo 'tralala' > /tmp/hahaha"
        ]]}}
    }

しかし、userDataスクリプトの内容をパラメーター/変数に保持して、再利用できるようにしたいと思います。

毎回コピーして貼り付ける必要なしに、そのようなbashスクリプトを再利用する機会はありますか?

8
walkeros

CloudFormationで定義された複数のEC2インスタンスのユーザーデータでbashスクリプトを再利用する方法に関するいくつかのオプションを次に示します。

1.デフォルトパラメータを文字列として設定します

最初に試みたソリューションは、マイナーな調整で機能するはずです。次のように、デフォルトのパラメーターを文字列として宣言する必要があります(JSONの代わりにYAMLを使用すると、複数行の文字列をインラインで宣言することが可能/簡単になります)。

  AWSTemplateFormatVersion: "2010-09-09"
  Parameters:
    myUserData:
      Type: String
      Default: |
        #!/bin/bash
        yum update -y
        # Install the files and packages from the metadata
        echo 'tralala' > /tmp/hahaha
(...)
  Resources:
    myEc2:
      Type: AWS::CloudFormation::Stack
      Properties
        TemplateURL: "s3://path/to/ec2-setup.yml"
        TimeoutInMinutes: 10
        Parameters:
          # (...)
          userData: !Ref myUserData

次に、ネストされたスタックで、必要な 組み込み関数Fn::Base64 、および Fn::Sub を適用します。 EC2インスタンスのリソースプロパティ内で Ref または Fn::GetAtt 関数をユーザーデータスクリプト内に適用する必要がある場合に非常に役立ちます。

  AWSTemplateFormatVersion: "2010-09-09"
  Parameters:
    # (...) some parameters here
    userData:
      Description: user data to be passed to instance
      Type: String
      Default: ""    
  Resources:
    EC2Instance:
      Type: AWS::EC2::Instance
      Properties:
        UserData:
          "Fn::Base64":
            "Fn::Sub": !Ref userData
        # (...) some other properties here
  # (...)

2.スクリプトをS3にアップロードします

単一のBashスクリプトをS3バケットにアップロードしてから、テンプレートの各EC2インスタンスに最小限のユーザーデータスクリプトを追加してスクリプトを呼び出すことができます。

  AWSTemplateFormatVersion: "2010-09-09"
  Parameters:
    # (...) some parameters here
    ScriptBucket:
      Description: S3 bucket containing user-data script
      Type: String
    ScriptKey:
      Description: S3 object key containing user-data script
      Type: String
  Resources:
    EC2Instance:
      Type: AWS::EC2::Instance
      Properties:
        UserData:
          "Fn::Base64":
            "Fn::Sub": |
              #!/bin/bash
              aws s3 cp s3://${ScriptBucket}/${ScriptKey} - | bash -s
        # (...) some other properties here
  # (...)

3.プリプロセッサを使用して、単一のソースからスクリプトをインライン化します

最後に、 troposphere のようなテンプレートプリプロセッサツールまたは独自のツールを使用して、よりコンパクトで表現力豊かなソースファイルから詳細なCloudFormation実行可能テンプレートを「生成」できます。このアプローチにより、ソースファイルの重複を排除できます。テンプレートには「重複」ユーザーデータスクリプトが含まれますが、これは生成されたテンプレートでのみ発生するため、問題は発生しません。

9
wjordan

同じユーザーデータを複数のテンプレートに提供するには、テンプレートを確認する必要がありますoutside。ここでの一般的なアプローチは、テンプレートをさらに1ステップ抽象化するか、「テンプレートをテンプレート化する」ことです。同じ方法を使用して両方のテンプレートを作成すると、両方をDRYのままにします。

私はクラウドフォーメーションの大ファンであり、特に本番環境での使用のために、ほとんどすべてのリソースを作成するためにそれを使用しています。しかし、それは強力ですが、それは完全にターンキーではありません。テンプレートの作成に加えて、スタックを作成するためにcoudformation APIを呼び出し、スタック名とパラメーターを指定する必要があります。したがって、クラウドフォーメーションの使用に関する自動化は、完全なソリューションの必要な部分です。この自動化は、単純化(bashスクリプトなど)または高度なものにすることができます。 ansibleのcloudformationモジュールを使用して、テンプレートの「周囲」を自動化することにしました。Jinjaでテンプレートのテンプレートを作成するか、同じ再利用可能なテンプレートに異なるパラメーターセットを提供するか、スタックが作成される前に検出を行います。必要な補助操作は何でも。この目的のために対流圏が本当に好きな人もいます-あなたがPythonの思想家なら、それがぴったりだと思うかもしれません。スタックの作成を処理するあらゆる種類の自動化が完了すると、テンプレート自体をより動的にするための手順を追加したり、再利用可能なコンポーネントから複数のスタックを組み立てたりするのが簡単になります。

職場ではクラウドフォーメーションをかなり使用しており、最近では、使用するテンプレートの共有コンポーネントを定義し、コンポーネントから実際のテンプレートを構成する構成アプローチを好む傾向があります。

もう1つのオプションは、条件を使用して2つのスタックをマージし、テンプレートから作成された特定のスタックに定義されたリソースを含めることを制御することです。これは単純な場合には問題なく機能しますが、これらすべての条件の組み合わせの複雑さにより、違いが本当に単純でない限り、長期的にはこれを困難な解決策にする傾向があります。

1
Dan Farrell

実際、私はすでに述べたよりももう1つの解決策を見つけました。このソリューションは、一方では少し「ハック」ですが、他方では、「bashスクリプト」のユースケース(および他のパラメーター)に非常に役立つことがわかりました。

アイデアは、値を出力する追加のスタック-"parameters stack"-を作成することです。スタックの出力は(デフォルト値の場合のように)文字列に限定されないため、base64でエンコードされたスクリプト全体をスタックからの単一の出力として定義できます。

欠点は、すべてのスタックで少なくとも1つのリソースを定義する必要があるため、parameters stackでも少なくとも1つのリソースを定義する必要があることです。この問題の解決策は、既存のリソースをすでに定義している別のテンプレートでパラメーターを定義するか、条件が満たされないために作成されない「偽のリソース」を作成することです。

ここでは、偽のリソースを使用したソリューションを紹介します。まず、次のように新しいparamaters-stack.jsonを作成します。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Outputs/returns parameter values",


  "Conditions" : {
    "alwaysFalseCondition" : {"Fn::Equals" : ["aaaaaaaaaa", "bbbbbbbbbb"]}
  },

  "Resources": {
    "FakeResource" : {
      "Type" : "AWS::EC2::EIPAssociation",
      "Condition" : "alwaysFalseCondition",
      "Properties" : {
        "AllocationId" :  { "Ref": "AWS::NoValue" },
        "NetworkInterfaceId" : { "Ref": "AWS::NoValue" }
      }
    }
  },

  "Outputs": {
    "ec2InitScript": {
      "Value":
      { "Fn::Base64" : { "Fn::Join" : ["", [
        "#!/bin/bash \n",
        "yum update -y \n",

        "# Install the files and packages from the metadata\n",
        "echo 'tralala' > /tmp/hahaha"
      ]]}}

    }
  }
}

ここで、メインテンプレートで、最初にparamters stackを宣言し、後でその出力を参照しますparameters stack

{
  "AWSTemplateFormatVersion": "2010-09-09",

   "Resources": { 

    "myParameters": {
      "Type": "AWS::CloudFormation::Stack",
      "Properties": {
        "TemplateURL": "s3://path/to/paramaters-stack.json",
        "TimeoutInMinutes": "10"
      }
    },

    "myEc2": {
      "Type": "AWS::CloudFormation::Stack",
      "Properties": {
        "TemplateURL": "s3://path/to/ec2-setup.json",
        "TimeoutInMinutes": "10",
        "Parameters": {
          // (...)
          "userData" : {"Fn::GetAtt": [ "myParameters", "Outputs.ec2InitScript" ]}
        }
     }
  }
}

1つのスタックファイルに最大60の出力を作成できるため、この手法を使用して1つのスタックファイルごとに60の変数/パラメーターを定義できることに注意してください。

0
walkeros