2013年4月24日

CloudFormation を使って Solr4 を動かす

AWS CloudFormation を使って Solr4 を動かしてみます。 こちらの記事をテンプレート化したものです。

初期設定などをテンプレートにまとめることで、素早く環境を構築できるとともにオペレーションミスを予防できます。Cloud Design Pattern の Stack Deploymentパターンに分類されるそうです。 devopscloud.com の "Lesson 5 - Infrastructure Automation" が順を追った説明になっていると思います。

とりあえず動かす

CloudFormation は JSON でテンプレートを定義します。 ユーザーガイドを読んで理解できれば良いのでしょうが、非常にボリュームが多いので何から始めるべきかに悩むと思います。 そこで、サンプルテンプレートから目的とするものを探し、ユーザーガイドを逆引きするのが良さそうです。

色々と試行錯誤してみた結果、下記の内容で Solr 4 が動くようになりました。 Apache 2.4, Tomcat 7, Solr 4 をインストールしてくれます。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters" : {
    "KeyName" : {
      "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the web server",
      "Type" : "String"
    }
  },
  "Mappings" : {
    "RegionMap" : {
      "us-east-1" : {"32" : "ami-5675ee3f", "64" : "ami-3275ee5b"},
      "us-west-1" : {"32" : "ami-d8d1fc9d", "64" : "ami-66d1fc23"},
      "us-west-2" : {"32" : "ami-d0be2ae0", "64" : "ami-ecbe2adc"},
      "eu-west-1" : {"32" : "ami-6893991c", "64" : "ami-44939930"},
      "ap-southeast-1" : {"32" : "ami-a29ed2f0", "64" : "ami-aa9ed2f8"},
      "ap-southeast-2" : {"32" : "ami-383eaf02", "64" : "ami-363eaf0c"},
      "ap-northeast-1" : {"32" : "ami-0f3fbf0e", "64" : "ami-173fbf16"},
      "sa-east-1" : {"32" : "ami-a56bb0b8", "64" : "ami-dd6bb0c0"}
    }
  },
  "Resources": {
    "SSHGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "Enable SSH access via port 22",
        "SecurityGroupIngress": [{
          "IpProtocol" : "tcp",
          "CidrIp" : "0.0.0.0/0",
          "FromPort" : "22", "ToPort" : "22"
        }]
      }
    },
    "WebGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "Enable HTTP access via port 80",
        "SecurityGroupIngress": [{
          "IpProtocol" : "tcp",
          "CidrIp" : "0.0.0.0/0",
          "FromPort" : "80", "ToPort" : "80"
        }]
      }
    },
    "CfnUser" : {
      "Type" : "AWS::IAM::User",
      "Properties" : {
        "Path": "/",
        "Policies": [{
          "PolicyName": "root",
          "PolicyDocument": { "Statement":[{
            "Effect":"Allow",
            "Action":"cloudformation:DescribeStackResource",
            "Resource":"*"
          }]}
        }]
      }
    },
    "HostKeys" : {
      "Type" : "AWS::IAM::AccessKey",
      "Properties" : {
        "UserName" : {"Ref": "CfnUser"}
      }
    },
    "WebServer" : {
      "Type" : "AWS::EC2::Instance",
      "Metadata" : {
        "AWS::CloudFormation::Init" : {
          "config" : {
            "packages" : {
              "yum" : {
                "httpd24" : [],
                "tomcat7" : [],
                "git-all" : [],
                "java-1.7.0-openjdk" : []
              }
            },
            "services" : {
              "sysvinit" : {
                "httpd" : {
                  "enabled" : "true",
                  "ensureRunning" : "true"
                },
                "tomcat7" : {
                  "enabled" : "true",
                  "ensureRunning" : "true"
                }
              }
            },
            "sources" : {
              "/opt/solr" : "http://ftp.jaist.ac.jp/pub/apache/lucene/solr/4.2.1/solr-4.2.1.tgz"
            }
          }
        }
      },
      "Properties" : {
        "ImageId" : { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "64" ]},
        "KeyName" : { "Ref" : "KeyName" },
        "InstanceType" : "t1.micro",
        "SecurityGroupIds" : [
          { "Ref" : "SSHGroup" },
          { "Ref" : "WebGroup" }
        ],
        "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [
          "#!/bin/bash -v\n",
          "date > /home/ec2-user/starttime\n",
          "yum update -y\n",
          "yum update -y aws-cfn-bootstrap\n",
          "# Install LAMP packages\n",
          "/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackName" }, " -r WebServer ",
          "    --access-key ",  { "Ref" : "HostKeys" },
          "    --secret-key ", {"Fn::GetAtt": ["HostKeys", "SecretAccessKey"]},
          "    --region ", { "Ref" : "AWS::Region" }, " || error_exit 'Failed to run cfn-init'\n",
          "cat <<EOF >/etc/httpd/conf.d/proxy.conf\n",
          "ProxyRequests Off\n",
          "ProxyPass /solr4 http://localhost:8080/solr4\n",
          "ProxyPassReverse /solr4 http://localhost:8080/solr4\n",
          "<Location \"/solr*\">\n",
          "    Order allow,deny\n",
          "    Allow from all\n",
          "</Location>\n",
          "EOF\n",
          "cat <<EOF >/usr/share/tomcat7/conf/Catalina/localhost/solr4.xml\n",
          "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n",
          "<Context docBase=\"/usr/local/solr4/solr.war\" debug=\"0\" crossContext=\"true\">\n",
          "    <Environment name=\"solr/home\" type=\"java.lang.String\" value=\"/usr/local/solr4\" override=\"true\"/>\n",
          "</Context>\n",
          "EOF\n",
          "cp -r /opt/solr/solr-4.2.1/example/multicore /usr/local/solr4\n",
          "cp /opt/solr/solr-4.2.1/dist/solr-4.2.1.war /usr/local/solr4/solr.war\n",
          "chown -R tomcat:tomcat /usr/local/solr4\n",
          "service httpd reload\n",
          "service tomcat7 restart\n"
        ]]}}
      }
    },
    "WebServerEip": {
      "Type": "AWS::EC2::EIP",
      "Properties": {
        "InstanceId": { "Ref": "WebServer" }
      }
    }
  },
  "Outputs" : {
    "WebsiteURL" : {
      "Value" : { "Fn::Join" : ["", ["http://", { "Fn::GetAtt" : [ "WebServer", "PublicDnsName" ]}]] },
      "Description" : "Apache front page"
    },
    "SolrURL" : {
      "Value" : { "Fn::Join" : ["", ["http://", { "Fn::GetAtt" : [ "WebServer", "PublicDnsName" ]}, "/solr4"]] },
      "Description" : "Solr Admin"
    },
    "WebServerAvailabilityZone" : {
      "Value" : { "Fn::GetAtt" : [ "WebServer", "AvailabilityZone" ]},
      "Description" : "AvailabilityZone of WebServer"
    },
    "SSHToWebServer" : {
      "Value" : { "Fn::Join" :["", [
        "ssh -i $HOME/.ssh/", { "Ref" : "KeyName" }, ".pem",
        " ec2-user@", { "Ref" : "WebServerEip" }
      ]] },
      "Description" : "SSH command to connect WebServer"
    }
  }
}

EDIT: gist を埋め込んでいましたが、更新していくのでテキスト貼付けに変更しました。

スタックを作成するときは "I acknowledge that this this template may create IAM resources" というチェックをオンにします。 インスタンスのメタデータを読み取る権限 ("cloudformation:DescribeStackResource") を追加するためです。

マネージメントコンソールから作業を実施すると、以下のようになります。 必要ならばタグ設定もできるようです。

インスタンスが起動すると、"Outputs" タブに IP アドレスなどが表示されます。

CloudFormation のテンプレートファイル

CloudFormation は宣言的にコンポーネントを記述していきます。 上記のテンプレートではざっくりと5つのものがあります。

  • AWSTemplateFormatVersion: フォーマットのバージョンを表す文字列で "2010-09-09" 固定です。
  • Parameters: ウィザードで与えるパラメーターを宣言します。
  • Mappings: 環境や入力に応じて可変にできるようなマッピングデータを宣言します。
  • Resources: AWS で利用するリソースを宣言します。
  • Outputs: インスタンス起動時に出力される情報を宣言します。

マッピングデータの例としては、 AMI イメージがあります。 こちらのページで ID を調べて、新しいものに更新しておきましょう。

設定の肝になるのは Resources の組み合わせです。 特に、最もよく使われそうなのが UserData で、インスタンス起動時に任意のスクリプトを実行できます。 CloudFormation の場合は cfn-init というスクリプトを使い、 インスタンスのメタデータから必要なパッケージ情報を読み取って設定できます。 Ref を使うことで、入力パラメータや実行時の情報を参照できます。 ユーティリティ群は Fn としてまとめられています。 属性を読み取る場合は "Fn::GetAtt"、文字列を連結する場合は "Fn::Join" を使います。

メタデータには "AWS::CloudFormation::Init" を指定できます。

利用パッケージ、デーモンなどのサービス設定だけでなく、任意のファイルを取得することも可能です。 S3 を参照することもできますし、Github など任意の URL にアクセスすることも可能です。 また、ZIP や Tar のようなアーカイブファイルの場合は、ダウンロード後に自動で展開してくれます。 上記のテンプレートファイルでは、この機能 (sources) を利用して Solr のアーカイブを取得しています。 コアの設定を HTTP アクセス可能な場所に置くことで、独自のスキーマ定義で Solr を起動できます。

終わりに

AWS CloudFormation を使って Solr4 を動かしてみました。 掲載例では1つのインスタンスしか扱っていませんが、 LAMP 構成で Web と DB を分離したい場合や、ロードバランサーを導入したい場合、 もしくは管理の都合で SSH アクセスを制限したい場合など、もっときめ細かく設定できますので、 様々なテンプレートに目を通してみると良さそうですね。

CloudFormation は単なるテンプレートですが、 システム運用を考えると Chef などの導入も有用になってくると考えられます。 AWS CloudFormation サンプルテンプレート の「白書のテンプレート」では Chef Solo、Chef Server、Chef Client、Puppet を使う場合が紹介されています。 ステップバイステップの説明としては、CloudFormation を使って Chef Solo をインストールする記事が分かりやすいと思いました。

コメントを投稿