想ひ出24: AWS/CloudFormationに入門する/2
おはようございます、moqrinです。
前回の概要に続き、今回はCloudFormationの基本的な対応のまとめです。
スタックの作成、更新という基本操作を行っていきます。
内容としては、新規VPCを作成して、パブリックサブネットとプライベートサブネットを各2つずつ、 ALBを作成してパブリックサブネットを紐付けて、RDSを作って、Auto Scaling グループ内に Amazon EC2 インスタンスを作成します。
AWS CloudFormation Designerから図示したものがこちらです。
中身としては、サンプルソリューションの「スケーラブルで耐久性の高い WordPress」に VPCとPublic,Privateのサブネットを新規追加したものですね。
目次
3. 宿題
1. テンプレートでスタックを新規作成
下記のテンプレートからスタックを作成します。
# VPC_Multi_RDS_AutoScaling_EC2_instance.json { "AWSTemplateFormatVersion": "2010-09-09", "Description": "AWS CloudFormation VPC_RDS_ALB_AutoScaling_EC2Instances", "Parameters" : { "KeyName": { "Description": "Name of an existing EC2 KeyPair to enable SSH access to the instances", "Type": "AWS::EC2::KeyPair::KeyName", "ConstraintDescription": "must be the name of an existing EC2 KeyPair." }, "DBName": { "Default": "myDatabase", "Description": "MySQL database name", "Type": "String", "MinLength": "8", "MaxLength": "64", "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." }, "DBUser": { "NoEcho": "true", "Default": "root", "Description": "Username for MySQL database access", "Type": "String", "MinLength": "3", "MaxLength": "16", "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." }, "DBPassword": { "NoEcho": "true", "Default": "samplePasswd", "Description": "Password for MySQL database access", "Type": "String", "MinLength": "8", "MaxLength": "41", "AllowedPattern": "[a-zA-Z0-9]*", "ConstraintDescription": "must contain only alphanumeric characters." }, "DBAllocatedStorage": { "Default": "5", "Description": "The size of the database (Gb)", "Type": "Number", "MinValue": "5", "MaxValue": "1024", "ConstraintDescription": "must be between 5 and 1024Gb." }, "DBInstanceClass": { "Description": "The database instance type", "Type": "String", "Default": "db.t2.micro", "AllowedValues": [ "db.t1.micro", "db.t2.micro" ], "ConstraintDescription": "must select a valid database instance type." }, "MultiAZDatabase": { "Default": "false", "Description": "Create a Multi-AZ MySQL Amazon RDS database instance", "Type": "String", "AllowedValues": [ "true", "false" ], "ConstraintDescription": "must be either true or false." }, "WebServerCapacity": { "Default": "1", "Description": "The initial number of WebServer instances", "Type": "Number", "MinValue": "1", "MaxValue": "3", "ConstraintDescription": "must be between 1 and 3 EC2 instances." }, "InstanceType": { "Description": "WebServer EC2 instance type", "Type": "String", "Default": "t2.micro", "AllowedValues": [ "t1.micro", "t2.nano", "t2.micro" ], "ConstraintDescription": "must be a valid EC2 instance type." }, "SSHLocation": { "Description": " The IP address range that can be used to SSH to the EC2 instances", "Type": "String", "MinLength": "9", "MaxLength": "18", "Default": "0.0.0.0/0", "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." } }, "Mappings" : { "AWSInstanceType2Arch" : { "t1.micro" : { "Arch" : "HVM64" }, "t2.nano" : { "Arch" : "HVM64" }, "t2.micro" : { "Arch" : "HVM64" } }, "AWSRegionArch2AMI" : { "us-east-1" : {"HVM64" : "ami-0ff8a91507f77f867", "HVMG2" : "ami-0a584ac55a7631c0c"}, "us-west-2" : {"HVM64" : "ami-a0cfeed8", "HVMG2" : "ami-0e09505bc235aa82d"}, "us-west-1" : {"HVM64" : "ami-0bdb828fd58c52235", "HVMG2" : "ami-066ee5fd4a9ef77f1"}, "ap-northeast-1" : {"HVM64" : "ami-00a5245b4816c38e6", "HVMG2" : "ami-053cdd503598e4a9d"} } }, "Resources" : { "VPC" : { "Type" : "AWS::EC2::VPC", "Properties" : { "CidrBlock" : "10.0.0.0/16", "Tags" : [ {"Key" : "Name", "Value" : "Sample-VPC" },{"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ] } }, "PublicSubnet1" : { "Type" : "AWS::EC2::Subnet", "Properties" : { "VpcId" : { "Ref" : "VPC" }, "CidrBlock" : "10.0.0.0/24", "AvailabilityZone" : { "Fn::Select" : [ "0", { "Fn::GetAZs" : { "Ref" : "AWS::Region" } }]}, "Tags" : [ {"Key" : "Name", "Value" : "PublicSubnet1" },{"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ] } }, "PublicSubnet2" : { "Type" : "AWS::EC2::Subnet", "Properties" : { "VpcId" : { "Ref" : "VPC" }, "CidrBlock" : "10.0.1.0/24", "AvailabilityZone" : { "Fn::Select" : [ "1", { "Fn::GetAZs" : { "Ref" : "AWS::Region" } }]}, "Tags" : [ {"Key" : "Name", "Value" : "PublicSubnet2" },{"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ] } }, "PrivateSubnet1" : { "Type" : "AWS::EC2::Subnet", "Properties" : { "VpcId" : { "Ref" : "VPC" }, "CidrBlock" : "10.0.2.0/24", "AvailabilityZone" : { "Fn::Select" : [ "0", { "Fn::GetAZs" : { "Ref" : "AWS::Region" } }]}, "Tags" : [ {"Key" : "Name", "Value" : "PrivateSubnet1" },{"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ] } }, "PrivateSubnet2" : { "Type" : "AWS::EC2::Subnet", "Properties" : { "VpcId" : { "Ref" : "VPC" }, "CidrBlock" : "10.0.3.0/24", "AvailabilityZone" : { "Fn::Select" : [ "1", { "Fn::GetAZs" : { "Ref" : "AWS::Region" } }]}, "Tags" : [ {"Key" : "Name", "Value" : "PrivateSubnet1" },{"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ] } }, "InternetGateway" : { "Type" : "AWS::EC2::InternetGateway", "Properties" : { "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ] } }, "AttachGateway" : { "Type" : "AWS::EC2::VPCGatewayAttachment", "Properties" : { "VpcId" : { "Ref" : "VPC" }, "InternetGatewayId" : { "Ref" : "InternetGateway" } } }, "PublicRouteTable" : { "Type" : "AWS::EC2::RouteTable", "Properties" : { "VpcId" : {"Ref" : "VPC"}, "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ] } }, "PublicRoute" : { "Type" : "AWS::EC2::Route", "DependsOn" : "AttachGateway", "Properties" : { "RouteTableId" : { "Ref" : "PublicRouteTable" }, "DestinationCidrBlock" : "0.0.0.0/0", "GatewayId" : { "Ref" : "InternetGateway" } } }, "Public1SubnetRouteTableAssociation" : { "Type" : "AWS::EC2::SubnetRouteTableAssociation", "Properties" : { "SubnetId" : { "Ref" : "PublicSubnet1" }, "RouteTableId" : { "Ref" : "PublicRouteTable" } } }, "Public2SubnetRouteTableAssociation" : { "Type" : "AWS::EC2::SubnetRouteTableAssociation", "Properties" : { "SubnetId" : { "Ref" : "PublicSubnet2" }, "RouteTableId" : { "Ref" : "PublicRouteTable" } } }, "NetworkAcl" : { "Type" : "AWS::EC2::NetworkAcl", "Properties" : { "VpcId" : {"Ref" : "VPC"}, "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ] } }, "InboundPublicNetworkAclEntry" : { "Type" : "AWS::EC2::NetworkAclEntry", "Properties" : { "CidrBlock" : "0.0.0.0/0", "Egress" : "false", "NetworkAclId" : { "Ref" : "NetworkAcl" }, "Protocol" : "-1", "RuleAction" : "allow", "RuleNumber" : "100" } }, "OutboundPublicNetworkAclEntry" : { "Type" : "AWS::EC2::NetworkAclEntry", "Properties" : { "CidrBlock" : "0.0.0.0/0", "Egress" : "true", "NetworkAclId" : { "Ref" : "NetworkAcl" }, "Protocol" : "-1", "RuleAction" : "allow", "RuleNumber" : "100" } }, "PublicSubnet1NetworkAclAssociation" : { "Type" : "AWS::EC2::SubnetNetworkAclAssociation", "Properties" : { "SubnetId" : { "Ref" : "PublicSubnet1" }, "NetworkAclId" : { "Ref" : "NetworkAcl" } } }, "PublicSubnet2NetworkAclAssociation" : { "Type" : "AWS::EC2::SubnetNetworkAclAssociation", "Properties" : { "SubnetId" : { "Ref" : "PublicSubnet2" }, "NetworkAclId" : { "Ref" : "NetworkAcl" } } }, "ApplicationLoadBalancer" : { "Type" : "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties" : { "SecurityGroups" : [{"Ref" : "ALBSecurityGroup"}], "Subnets" : [{"Ref": "PublicSubnet1"},{ "Ref" : "PublicSubnet2" }], "Tags" : [ {"Key" : "Name", "Value" : "Sample-ALB" } ] } }, "ALBListener" : { "Type" : "AWS::ElasticLoadBalancingV2::Listener", "Properties" : { "DefaultActions" : [{ "Type" : "forward", "TargetGroupArn" : { "Ref" : "ALBTargetGroup" } }], "LoadBalancerArn" : { "Ref" : "ApplicationLoadBalancer" }, "Port" : "80", "Protocol" : "HTTP" } }, "ALBTargetGroup" : { "Type" : "AWS::ElasticLoadBalancingV2::TargetGroup", "Properties" : { "HealthCheckIntervalSeconds" : 10, "HealthCheckTimeoutSeconds" : 5, "HealthyThresholdCount" : 2, "Port" : 80, "Protocol" : "HTTP", "UnhealthyThresholdCount" : 5, "VpcId" : {"Ref" : "VPC"} } }, "WebServerGroup" : { "Type" : "AWS::AutoScaling::AutoScalingGroup", "Properties" : { "VPCZoneIdentifier" : [{"Ref": "PublicSubnet1"},{ "Ref" : "PublicSubnet2" }], "LaunchConfigurationName" : { "Ref" : "LaunchConfig" }, "MinSize" : "1", "MaxSize" : "3", "DesiredCapacity" : { "Ref" : "WebServerCapacity" }, "TargetGroupARNs" : [ { "Ref" : "ALBTargetGroup" } ] }, "UpdatePolicy": { "AutoScalingRollingUpdate": { "MinInstancesInService": "1", "MaxBatchSize": "1", "PauseTime" : "PT15M", "WaitOnResourceSignals": "true" } } }, "DBSubnetGroup" : { "Type" : "AWS::RDS::DBSubnetGroup", "Properties" : { "DBSubnetGroupDescription" : "This is a privateSubnet", "SubnetIds": [{"Ref": "PrivateSubnet1"},{ "Ref" : "PrivateSubnet2" }] } }, "LaunchConfig": { "Type" : "AWS::AutoScaling::LaunchConfiguration", "Properties": { "AssociatePublicIpAddress": true, "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" }, { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] }, "InstanceType" : { "Ref" : "InstanceType" }, "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ], "KeyName" : { "Ref" : "KeyName" } } }, "WebServerSecurityGroup" : { "Type" : "AWS::EC2::SecurityGroup", "Properties" : { "GroupDescription" : "Enable HTTP access via port 80 locked down to the ELB and SSH access", "SecurityGroupIngress" : [ { "IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"}, {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "SSHLocation"} } ], "VpcId" : { "Ref" : "VPC" } } }, "ALBSecurityGroup" : { "Type" : "AWS::EC2::SecurityGroup", "Properties" : { "GroupName": "http", "GroupDescription" : "Enable HTTP access via port 80", "SecurityGroupIngress" : [ { "IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"}], "VpcId" : { "Ref" : "VPC" } } }, "DBEC2SecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties" : { "GroupDescription": "Open database for access", "SecurityGroupIngress" : [{ "IpProtocol" : "tcp", "FromPort" : "3306", "ToPort" : "3306", "SourceSecurityGroupId" : { "Ref" : "WebServerSecurityGroup" } }], "VpcId" : { "Ref" : "VPC" } } }, "MySQLDatabase": { "Type": "AWS::RDS::DBInstance", "Properties": { "Engine" : "MySQL", "DBName" : { "Ref": "DBName" }, "MultiAZ" : { "Ref": "MultiAZDatabase" }, "MasterUsername": { "Ref": "DBUser" }, "MasterUserPassword": { "Ref" : "DBPassword" }, "DBInstanceClass": { "Ref" : "DBInstanceClass" }, "AllocatedStorage": { "Ref" : "DBAllocatedStorage" }, "DBSubnetGroupName": { "Ref": "DBSubnetGroup" }, "VPCSecurityGroups": [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ] } } }, "Outputs" : { "WebsiteURL" : { "Description" : "URL for newly created LAMP stack", "Value" : { "Fn::Join" : ["", ["http://", { "Fn::GetAtt" : [ "ApplicationLoadBalancer", "DNSName" ]}]] } } } }
ローカルにあるテンプレート内容に問題がないか確認しておきます。
# テンプレートの検証 aws cloudformation validate-template --template-body file://`pwd`/VPC_Multi_RDS_AutoScaling_EC2_instance.json
問題なければスタックを作成します。 (パラメータ指定については、インスタンスタイプ、DB名、DBユーザー、DBパスワードなど省略してしまっていますー。)
# スタックの新規作成 aws cloudformation create-stack --stack-name sample-stack --template-body file://`pwd`/VPC_Multi_RDS_AutoScaling_EC2_instance.json \ --parameters ParameterKey=KeyName,ParameterValue=YOUR_KEY_NAME\ ParameterKey=SSHLocation,ParameterValue=xxx.xxx.xxx.xxx/32
できました。
アプリケーションを何も入れていないので、ALBの方ではunhealtyになっています。
なので、Wordpressでもインストールしてみますか。
2. スタックを変更セットを使って更新
変更セットを使用すると、スタックの変更案が実行中のリソースに与える可能性がある影響 (たとえば、変更によって重要なリソースが削除されたり置き換えられたりしないか) を確認できたり、変更セットの実行を確定したときに限り、スタックが変更されるため、変更案のまま続行するか別の変更セットを作成して他の変更を検討するかを決定できるようです。
まずは、既存のテンプレート内容を取得します。
# スタックを取得 aws cloudformation get-template --stack-name sample-stack > VPC_Multi_RDS_AutoScaling_EC2_instance_wp.json
元々のサンプルから"LaunchConfig"の部分をそのまま上書いて、諸々修正したのが下記テンプレートです。(冗長)
# スタックを取得して加筆修正 { "Description": "AWS CloudFormation VPC_RDS_ALB_AutoScaling_EC2Instances", "Parameters": { "MultiAZDatabase": { "Default": "false", "ConstraintDescription": "must be either true or false.", "Type": "String", "Description": "Create a Multi-AZ MySQL Amazon RDS database instance", "AllowedValues": [ "true", "false" ] }, "DBAllocatedStorage": { "Description": "The size of the database (Gb)", "Default": "5", "Type": "Number", "MaxValue": "1024", "MinValue": "5", "ConstraintDescription": "must be between 5 and 1024Gb." }, "InstanceType": { "Default": "t2.micro", "ConstraintDescription": "must be a valid EC2 instance type.", "Type": "String", "Description": "WebServer EC2 instance type", "AllowedValues": [ "t1.micro", "t2.nano", "t2.micro" ] }, "SSHLocation": { "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x.", "Description": " The IP address range that can be used to SSH to the EC2 instances", "Default": "0.0.0.0/0", "MinLength": "9", "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", "MaxLength": "18", "Type": "String" }, "KeyName": { "ConstraintDescription": "must be the name of an existing EC2 KeyPair.", "Type": "AWS::EC2::KeyPair::KeyName", "Description": "Name of an existing EC2 KeyPair to enable SSH access to the instances" }, "DBPassword": { "ConstraintDescription": "must contain only alphanumeric characters.", "Description": "Password for MySQL database access", "Default": "samplePasswd", "MinLength": "8", "AllowedPattern": "[a-zA-Z0-9]*", "NoEcho": "true", "MaxLength": "41", "Type": "String" }, "DBUser": { "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters.", "Description": "Username for MySQL database access", "Default": "root", "MinLength": "4", "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", "NoEcho": "true", "MaxLength": "16", "Type": "String" }, "DBInstanceClass": { "Default": "db.t2.micro", "ConstraintDescription": "must select a valid database instance type.", "Type": "String", "Description": "The database instance type", "AllowedValues": [ "db.t1.micro", "db.t2.micro" ] }, "DBName": { "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters.", "Description": "MySQL database name", "Default": "myDatabase", "MinLength": "4", "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", "MaxLength": "64", "Type": "String" }, "WebServerCapacity": { "Description": "The initial number of WebServer instances", "Default": "1", "Type": "Number", "MaxValue": "3", "MinValue": "1", "ConstraintDescription": "must be between 1 and 3 EC2 instances." } }, "AWSTemplateFormatVersion": "2010-09-09", "Outputs": { "WebsiteURL": { "Description": "URL for newly created LAMP stack", "Value": { "Fn::Join": [ "", [ "http://", { "Fn::GetAtt": [ "ApplicationLoadBalancer", "DNSName" ] } ] ] } } }, "Resources": { "NetworkAcl": { "Type": "AWS::EC2::NetworkAcl", "Properties": { "VpcId": { "Ref": "VPC" }, "Tags": [ { "Value": { "Ref": "AWS::StackId" }, "Key": "Application" } ] } }, "PublicRoute": { "Type": "AWS::EC2::Route", "Properties": { "GatewayId": { "Ref": "InternetGateway" }, "DestinationCidrBlock": "0.0.0.0/0", "RouteTableId": { "Ref": "PublicRouteTable" } }, "DependsOn": "AttachGateway" }, "PublicSubnet1NetworkAclAssociation": { "Type": "AWS::EC2::SubnetNetworkAclAssociation", "Properties": { "SubnetId": { "Ref": "PublicSubnet1" }, "NetworkAclId": { "Ref": "NetworkAcl" } } }, "InternetGateway": { "Type": "AWS::EC2::InternetGateway", "Properties": { "Tags": [ { "Value": { "Ref": "AWS::StackId" }, "Key": "Application" } ] } }, "Public1SubnetRouteTableAssociation": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "SubnetId": { "Ref": "PublicSubnet1" }, "RouteTableId": { "Ref": "PublicRouteTable" } } }, "ALBListener": { "Type": "AWS::ElasticLoadBalancingV2::Listener", "Properties": { "Protocol": "HTTP", "DefaultActions": [ { "TargetGroupArn": { "Ref": "ALBTargetGroup" }, "Type": "forward" } ], "LoadBalancerArn": { "Ref": "ApplicationLoadBalancer" }, "Port": "80" } }, "ALBSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "SecurityGroupIngress": [ { "ToPort": "80", "IpProtocol": "tcp", "CidrIp": "0.0.0.0/0", "FromPort": "80" } ], "GroupName": "http", "VpcId": { "Ref": "VPC" }, "GroupDescription": "Enable HTTP access via port 80" } }, "LaunchConfig": { "Type" : "AWS::AutoScaling::LaunchConfiguration", "Metadata" : { "AWS::CloudFormation::Init" : { "configSets" : { "wordpress_install" : ["install_cfn", "install_wordpress" ] }, "install_cfn" : { "files": { "/etc/cfn/cfn-hup.conf": { "content": { "Fn::Join": [ "", [ "[main]\n", "stack=", { "Ref": "AWS::StackId" }, "\n", "region=", { "Ref": "AWS::Region" }, "\n" ]]}, "mode" : "000400", "owner" : "root", "group" : "root" }, "/etc/cfn/hooks.d/cfn-auto-reloader.conf": { "content": { "Fn::Join": [ "", [ "[cfn-auto-reloader-hook]\n", "triggers=post.update\n", "path=Resources.LaunchConfig.Metadata.AWS::CloudFormation::Init\n", "action=/opt/aws/bin/cfn-init -v ", " --stack ", { "Ref" : "AWS::StackName" }, " --resource LaunchConfig ", " --configsets wordpress_install ", " --region ", { "Ref" : "AWS::Region" }, "\n" ]]}, "mode" : "000400", "owner" : "root", "group" : "root" } }, "services" : { "sysvinit" : { "cfn-hup" : { "enabled" : "true", "ensureRunning" : "true", "files" : ["/etc/cfn/cfn-hup.conf", "/etc/cfn/hooks.d/cfn-auto-reloader.conf"]} } } }, "install_wordpress" : { "packages" : { "yum" : { "php" : [], "php-mysql" : [], "mysql" : [], "httpd" : [] } }, "sources" : { "/var/www/html" : "http://wordpress.org/latest.tar.gz" }, "files" : { "/tmp/create-wp-config" : { "content" : { "Fn::Join" : [ "", [ "#!/bin/bash\n", "cp /var/www/html/wordpress/wp-config-sample.php /var/www/html/wordpress/wp-config.php\n", "sed -i \"s/'database_name_here'/'",{ "Ref" : "DBName" }, "'/g\" wp-config.php\n", "sed -i \"s/'username_here'/'",{ "Ref" : "DBUser" }, "'/g\" wp-config.php\n", "sed -i \"s/'password_here'/'",{ "Ref" : "DBPassword" }, "'/g\" wp-config.php\n", "sed -i \"s/'localhost'/'",{ "Fn::GetAtt" : [ "MySQLDatabase", "Endpoint.Address" ] }, "'/g\" wp-config.php\n" ]]}, "mode" : "000500", "owner" : "root", "group" : "root" } }, "commands" : { "01_configure_wordpress" : { "command" : "/tmp/create-wp-config", "cwd" : "/var/www/html/wordpress" } }, "services" : { "sysvinit" : { "httpd" : { "enabled" : "true", "ensureRunning" : "true" } } } } } }, "Properties": { "AssociatePublicIpAddress" : true, "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" }, { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] }, "InstanceType" : { "Ref" : "InstanceType" }, "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ], "KeyName" : { "Ref" : "KeyName" }, "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ "#!/bin/bash -xe\n", "yum update -y aws-cfn-bootstrap\n", "/opt/aws/bin/cfn-init -v ", " --stack ", { "Ref" : "AWS::StackName" }, " --resource LaunchConfig ", " --configsets wordpress_install ", " --region ", { "Ref" : "AWS::Region" }, "\n", "/opt/aws/bin/cfn-signal -e $? ", " --stack ", { "Ref" : "AWS::StackName" }, " --resource WebServerGroup ", " --region ", { "Ref" : "AWS::Region" }, "\n" ]]}} } }, "DBSubnetGroup": { "Type": "AWS::RDS::DBSubnetGroup", "Properties": { "SubnetIds": [ { "Ref": "PrivateSubnet1" }, { "Ref": "PrivateSubnet2" } ], "DBSubnetGroupDescription": "This is a privateSubnet" } }, "VPC": { "Type": "AWS::EC2::VPC", "Properties": { "CidrBlock": "10.0.0.0/16", "Tags": [ { "Value": "Sample-VPC", "Key": "Name" }, { "Value": { "Ref": "AWS::StackId" }, "Key": "Application" } ] } }, "AttachGateway": { "Type": "AWS::EC2::VPCGatewayAttachment", "Properties": { "VpcId": { "Ref": "VPC" }, "InternetGatewayId": { "Ref": "InternetGateway" } } }, "MySQLDatabase": { "Type": "AWS::RDS::DBInstance", "Properties": { "Engine": "MySQL", "MultiAZ": { "Ref": "MultiAZDatabase" }, "MasterUsername": { "Ref": "DBUser" }, "MasterUserPassword": { "Ref": "DBPassword" }, "VPCSecurityGroups": [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ], "AllocatedStorage": { "Ref": "DBAllocatedStorage" }, "DBInstanceClass": { "Ref": "DBInstanceClass" }, "DBSubnetGroupName": { "Ref": "DBSubnetGroup" }, "DBName": { "Ref": "DBName" } } }, "PublicRouteTable": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { "Ref": "VPC" }, "Tags": [ { "Value": { "Ref": "AWS::StackId" }, "Key": "Application" } ] } }, "PrivateSubnet2": { "Type": "AWS::EC2::Subnet", "Properties": { "Tags": [ { "Value": "PrivateSubnet1", "Key": "Name" }, { "Value": { "Ref": "AWS::StackId" }, "Key": "Application" } ], "VpcId": { "Ref": "VPC" }, "CidrBlock": "10.0.3.0/24", "AvailabilityZone": { "Fn::Select": [ "1", { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] } } }, "PrivateSubnet1": { "Type": "AWS::EC2::Subnet", "Properties": { "Tags": [ { "Value": "PrivateSubnet1", "Key": "Name" }, { "Value": { "Ref": "AWS::StackId" }, "Key": "Application" } ], "VpcId": { "Ref": "VPC" }, "CidrBlock": "10.0.2.0/24", "AvailabilityZone": { "Fn::Select": [ "0", { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] } } }, "PublicSubnet2NetworkAclAssociation": { "Type": "AWS::EC2::SubnetNetworkAclAssociation", "Properties": { "SubnetId": { "Ref": "PublicSubnet2" }, "NetworkAclId": { "Ref": "NetworkAcl" } } }, "InboundPublicNetworkAclEntry": { "Type": "AWS::EC2::NetworkAclEntry", "Properties": { "NetworkAclId": { "Ref": "NetworkAcl" }, "RuleNumber": "100", "Protocol": "-1", "Egress": "false", "RuleAction": "allow", "CidrBlock": "0.0.0.0/0" } }, "WebServerSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "SecurityGroupIngress": [ { "ToPort": "80", "IpProtocol": "tcp", "CidrIp": "0.0.0.0/0", "FromPort": "80" }, { "ToPort": "22", "IpProtocol": "tcp", "CidrIp": { "Ref": "SSHLocation" }, "FromPort": "22" } ], "VpcId": { "Ref": "VPC" }, "GroupDescription": "Enable HTTP access via port 80 locked down to the ELB and SSH access" } }, "WebServerGroup": { "Type": "AWS::AutoScaling::AutoScalingGroup", "Properties": { "DesiredCapacity": { "Ref": "WebServerCapacity" }, "MinSize": "1", "MaxSize": "3", "VPCZoneIdentifier": [ { "Ref": "PublicSubnet1" }, { "Ref": "PublicSubnet2" } ], "LaunchConfigurationName": { "Ref": "LaunchConfig" }, "TargetGroupARNs": [ { "Ref": "ALBTargetGroup" } ] }, "UpdatePolicy": { "AutoScalingRollingUpdate": { "PauseTime": "PT15M", "WaitOnResourceSignals": "true", "MaxBatchSize": "1", "MinInstancesInService": "1" } } }, "ApplicationLoadBalancer": { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { "Subnets": [ { "Ref": "PublicSubnet1" }, { "Ref": "PublicSubnet2" } ], "SecurityGroups": [ { "Ref": "ALBSecurityGroup" } ], "Tags": [ { "Value": "Sample-ALB", "Key": "Name" } ] } }, "PublicSubnet1": { "Type": "AWS::EC2::Subnet", "Properties": { "Tags": [ { "Value": "PublicSubnet1", "Key": "Name" }, { "Value": { "Ref": "AWS::StackId" }, "Key": "Application" } ], "VpcId": { "Ref": "VPC" }, "CidrBlock": "10.0.0.0/24", "AvailabilityZone": { "Fn::Select": [ "0", { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] } } }, "DBEC2SecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "SecurityGroupIngress": [ { "ToPort": "3306", "IpProtocol": "tcp", "SourceSecurityGroupId": { "Ref": "WebServerSecurityGroup" }, "FromPort": "3306" } ], "VpcId": { "Ref": "VPC" }, "GroupDescription": "Open database for access" } }, "PublicSubnet2": { "Type": "AWS::EC2::Subnet", "Properties": { "Tags": [ { "Value": "PublicSubnet2", "Key": "Name" }, { "Value": { "Ref": "AWS::StackId" }, "Key": "Application" } ], "VpcId": { "Ref": "VPC" }, "CidrBlock": "10.0.1.0/24", "AvailabilityZone": { "Fn::Select": [ "1", { "Fn::GetAZs": { "Ref": "AWS::Region" } } ] } } }, "Public2SubnetRouteTableAssociation": { "Type": "AWS::EC2::SubnetRouteTableAssociation", "Properties": { "SubnetId": { "Ref": "PublicSubnet2" }, "RouteTableId": { "Ref": "PublicRouteTable" } } }, "OutboundPublicNetworkAclEntry": { "Type": "AWS::EC2::NetworkAclEntry", "Properties": { "NetworkAclId": { "Ref": "NetworkAcl" }, "RuleNumber": "100", "Protocol": "-1", "Egress": "true", "RuleAction": "allow", "CidrBlock": "0.0.0.0/0" } }, "ALBTargetGroup": { "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", "Properties": { "HealthCheckIntervalSeconds": 10, "VpcId": { "Ref": "VPC" }, "Protocol": "HTTP", "UnhealthyThresholdCount": 5, "HealthyThresholdCount": 2, "HealthCheckTimeoutSeconds": 5, "Port": 80 } } }, "Mappings": { "AWSInstanceType2Arch": { "t2.micro": { "Arch": "HVM64" }, "t2.nano": { "Arch": "HVM64" }, "t1.micro": { "Arch": "HVM64" } }, "AWSRegionArch2AMI": { "us-west-2": { "HVM64": "ami-a0cfeed8", "HVMG2": "ami-0e09505bc235aa82d" }, "us-east-1": { "HVM64": "ami-0ff8a91507f77f867", "HVMG2": "ami-0a584ac55a7631c0c" }, "ap-northeast-1": { "HVM64": "ami-00a5245b4816c38e6", "HVMG2": "ami-053cdd503598e4a9d" }, "us-west-1": { "HVM64": "ami-0bdb828fd58c52235", "HVMG2": "ami-066ee5fd4a9ef77f1" } } } }
確認してみてOKだったら、変更セットを作成します。
# 変更セットの作成 aws cloudformation create-change-set --stack-name sample-stack \ --change-set-name sample-change-set --template-body file://`pwd`/VPC_Multi_RDS_AutoScaling_EC2_instance_wp.json \ --parameters ParameterKey=KeyName,ParameterValue=YOUR_KEY_NAME \ ParameterKey=SSHLocation,ParameterValue=xxx.xxx.xxx.xxx/32
変更セットを表示します。
# 変更セットの表示 aws cloudformation list-change-sets --stack-name sample-stack
表示した変更セット ID を指定して変更セットに関する情報を取得します。
(ARNじゃなきゃあかんです。下記のコマンドはsampleのarnです。)
# 変更セットの表示 aws cloudformation describe-change-set --change-set-name arn:aws:cloudformation:ap-northeast-1:123456789012:stack/sample-change-set/1a2345b6-0000-00a0-a123-00abc0abc000"
変更セットを実行します。 変更セットを実行すると、スタックに関連付けられた変更セットは AWS CloudFormation によってすべて削除されます。
# 変更セットの実行 aws cloudformation execute-change-set --change-set-name arn:aws:cloudformation:ap-northeast-1:123456789012:stack/sample-change-set/1a2345b6-0000-00a0-a123-00abc0abc000"
進行状況を確認します。
# 進行状況を確認 aws cloudformation describe-stacks --stack-name sample-stack
完了したら、変更セットを確認
# 変更セットを確認 aws cloudformation list-change-sets --stack-name sample-stack
更新が完了しているので、出力画面からALBのリンクに飛びます。
あ、あれ!? Apacheのトップページですけど、、、WP的な画面じゃないですね。。 何で?? (ちなみに、元々のサンプルをそのまま実行してみても何かうまくいきませんでした、、、主題ではないので、また後日、再度詳細の確認するようにしますー。。)
ということで、スタックを削除します。
# 変更セットを確認 aws cloudformation delete-stack --stack-name sample-stack
3. 宿題
CloudFormation ヘルパースクリプトの確認
テンプレートのネスト化
何故かBlackBeltの資料には触られていないCloudFormerも利用してみたいですね。
所感
今回も実際に手を動かしてみて、色々と当惑しつつ、お勉強になりました。
NetworkACLをインバウンド&アウトバウンド含めて明示的に設定してあげないとダメとか。
DB用のSubnetを作成してあげないとダメとか。
指定したVPC内にDB作成するには、DBSubnetGroupNameが必要とか。
参考:
想ひ出23: AWS/CloudFormationに入門する/1
おはようございます、moqrinです。
今更感満載ですが、CloudFormationに入門したので、その学習まとめ1です。
しばらく触らないと忘れるため、個人的後日振り返り用でございます。
なお、また後日、学習を続けてまとめるつもりでございます。
内容としては、公式リファレンスを組み換えたまとめです。
目次
2. テンプレートの作成方法
3. テンプレートの基礎項目
4. 主な組み込み関数
5. その他の重要機能
6. AWS CLIの利用
1. AWS CloudFormationの仕組み
テンプレートを作成
テンプレートをローカルまたは S3 バケットに保存。
ローカルに保存されたテンプレートファイルを指定した場合、AWS CloudFormation はそれを AWS アカウントの S3 バケットにアップロードする
テンプレートのファイルの場所 (ローカルコンピューターのパスや Amazon S3 の URL など) を指定し、AWS CloudFormation スタックを作成する。
用語
テンプレート --- JSON または YAML 形式のテキストファイル。AWS リソースを作成する際の設計図として使用する。
リソースの依存関係はAWS CloudFormationで自動判別してくれる。
スタック --- テンプレートで作成されたリソースのコレクションを管理する単位。スタックを作成、更新、削除することで、リソースのコレクションを作成、更新、削除する。
スタックを削除しても、その中のいくつかのリソースは保持したい場合には、削除ポリシーを使用してそのリソースを保持できる。
2. テンプレートの作成(利用)方法
クイックスタート
サンプルテンプレート & テンプレートスニペット
サンプルからリファレンスを確認して、カスタマイズを施す
ゼロからテンプレート書く
ツールによる書式・構文チェックを活用 VSCodeの"CloudFormation support for Visual Studio Code"
https://marketplace.visualstudio.com/items?itemName=aws-scripting-guy.cform
Linterとしてcfn-python-lint CLIから"aws cloudformation validate-template"よりこちらの方が範囲が広いらしい! (validate-templateで確認していました・・・後日使ってみる。)
https://github.com/awslabs/cfn-python-lint https://marketplace.visualstudio.com/items?itemName=kddejong.vscode-cfn-lint
3. テンプレートの基礎項目
項目 | 内容 |
---|---|
Resources | ※ 必須項目 スタックを構成するリソースとプロパティを指定 |
Parameters | 実行時にユーザ入力を求めるパラメータ値(Keypair名やDBユーザ名など) |
Mappings | キーと値のマッピング。パラメータ値の条件指定に使用 |
Outputs | スタック構築後にAWS CloudFormationから出力させる値(DNSやEIPなど) |
Transform | サーバレスアプリケーションの場合に使用。使用するSAMのバージョンを指定 |
Description | テンプレートの説明文 |
Metadata | テンプレートに関する追加情報 |
AWSTemplateFormatVersion | テンプレートのバージョン |
4. 主な組み込み関数
関数 | 内容 |
---|---|
!Ref | テンプレート内の論理名(ID)から物理名(ID)を参照する |
!GetAtt | リソースが持つ属性値を取得する |
!Join | 文字列の結合 |
!GetAZs | AZを取得する擬似パラメーター |
Fn::ImportValue | エクスポート値を取得する |
Fn::Base64 | Base64エンコード |
Fn::If,Fn::Equals,Fn::Not | 条件関数 |
5. その他の重要機能
- DependsOn 属性: あるリソースに続けて別のリソースを作成する必要があることを指定する
DeletionPolicy 属性: リソースの削除を AWS CloudFormation でどのように処理するかを指定する
Metadata 属性: リソースを含む構造化データを指定する
6. AWS CLIの使用
# テンプレートの検証 aws cloudformation validate-template --template-url file:///home/testuser/mytemplate.json
# 新規作成 aws cloudformation create-stack --stack-name myteststack --template-body file:///home/testuser/mytemplate.json --parameters ParameterKey=Parm1,ParameterValue=test1 ParameterKey=Parm2,ParameterValue=test2
# 新規作成/複数パラメータ aws cloudformation create-stack --stack-name myteststack --template-body file:///home/testuser/mytemplate.json --parameters ParameterKey=Parm1,ParameterValue=test1 ParameterKey=SubnetIDs,ParameterValue=SubnetID1\\,SubnetID2
# 更新 aws cloudformation update-stack --stack-name myteststack --template-body file:///home/testuser/mytemplate.json --parameters ParameterKey=Parm1,ParameterValue=test1 ParameterKey=Parm2,ParameterValue=test2
# 削除 aws cloudformation delete-stack --stack-name myteststack
7. CloudFormation ヘルパースクリプト
Amazon EC2 インスタンスでソフトウェアをインストールしたりサービスを開始したりするために使用できる、Python ヘルパースクリプト。
後日の宿題。。。
CloudFormation ヘルパースクリプトリファレンス
参考:
想ひ出22: 小ネタ/Grafana5.3/G Suiteメール送信 備忘録
おはようございます、moqrinです。
今記事は、Grafanaをいじっている際に発生したメール送信のための設定の備忘録です。
PostfixからG Suite経由でメール送信をします。
こちらの記事で設定していますが、記事が更新されていないようで、
完全にマッチはしていなそうです。
SMTP リレー: Gmail 以外の送信メールを Google 経由にする
やること
1. [Apps] > [G Suite] > [Gmail] > [詳細設定] にアクセスする
2. 包括的なメール ストレージをオンにする
3. [SMTP リレーサービス] 設定までスクロールし、カーソルを合わせて [設定]する
4. /etc/postfix/main.cfを編集する
5. /etc/grafana/grafana.iniを編集する
6. 確認
1. [Apps] > [G Suite] > [Gmail] > [詳細設定] にアクセスする
参照記事通りに、アクセスします。
2. 包括的なメール ストレージをオンにする
3. [SMTP リレーサービス] 設定までスクロールし、カーソルを合わせて [設定]する
設定の選択肢はいくつかあるのですが、今回はドメイン内のアドレスからのみ送信許可としました。
4. /etc/postfix/main.cfを編集する
いちおう原本は残しておきます。
cp /etc/postfix/main.cf /etc/postfix/main.cf.org
# vim /etc/postfix/main.cf myhostname = grafana.YOUR_DOMAIN_NAME mydomain = YOUR_DOMAIN_NAME myorigin = $myhostname relayhost = smtp-relay.gmail.com:587
5. /etc/grafana/grafana.iniを編集する
# vim /etc/grafana/grafana.ini [smtp] - enabled = false + enabled = true host = localhost:25 from_address = grafana@YOUR_DOMAIN_NAME
6. 確認
わーい、来ましたー。
参考:
想ひ出21: GCP/CloudSQL/Nginx/Zabbix4.0/Grafana5.3/Slack
おはようございます、moqrinです。
今記事は、Zabbix4.0がリリースされたそうなので、
なるほど。とりあえずGCPで設定してみた、というだけの備忘録です。
基本的には下記の素晴らしい諸先輩方に依っておりますので、
わたしのクソブログでなくてもOKです!
だいたい下記、手動で対応して45分くらいですかね。。
やること
1. 準備しよう
2. Zabbix,MySQL5.7のインストール
3. Zabbix データベースとユーザーを作成
4. Zabbix-server設定
5. Zabbix-webの権限周り設定
6. Nginx php-fpmの設定
7. Zabbixの設定
8. Grafana導入
9. Zabbixエージェントの設定
10. (ついでに)Slackアラート
1. 準備しよう
とりあえずCloudSQLを作成しておきましょう。(ちょっと時間かかるしね。)
(ZabbixサーバーとSQLインスタンスのリージョン&ゾーンを合わせておくとローカルIPで接続できるね!)
Port | Use | Server |
---|---|---|
80 or 443 | http or https | Zabbixサーバー |
3000 | Grafana | Zabbixサーバー |
10050 | Zabbixサーバー → Zabbixエージェント | 監視サーバー(Zabbixサーバー含む) |
10051 | Zabbixエージェント → Zabbixサーバー | Zabbixサーバー |
Remiリポジトリのインストールします。
rpm -ivh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
php7.2を利用するためにリポジトリ編集しておきます。(php7.3でも良いけど。)
# vim /etc/yum.repos.d/remi.repo - enabled=0 + enabled=1
# vim /etc/yum.repos.d/remi-php72.repo - enabled=0 + enabled=1
悪さするみたいなので、SELINUXさんを落とします。
# vim /etc/sysconfig/selinux - SELINUX=enforcing + SELINUX=disabled
そして再起動。
reboot
(いちおう確認しときますか。)
getenforce
2. Zabbix,MySQL5.7のインストール
Zabbix4.0インストールします。
参考URL:
Install and configure Zabbix server
rpm -ivh https://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-release-4.0-1.el7.noarch.rpm
yum -y install zabbix-server-mysql zabbix-web-mysql zabbix-web-japanese zabbix-agent zabbix-get
mysql5.7インストールしまーす。(CloudSQLがMySQL5.7だから)
rpm -Uvh http://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm
yum install -y --enablerepo=mysql57-community mysql-community-server
確認しときます。
zabbix_server -V
3. Zabbix データベースとユーザーを作成
Cloud SQLが接続できるようになっているか、設定を確認しておきます。
DBに接続します。
mysql -u root -h CLOUD_SQL_IP_ADDRESS -p
データベースおよびユーザーを作成します。
CREATE DATABASE zabbix character set utf8 collate utf8_bin; CREATE USER zabbix@'%'IDENTIFIED BY 'YOUR_DB_PASSWORD'; GRANT ALL ON zabbix.* TO zabbix@'%';
初期データ、スキーマを投入します。
cd /usr/share/doc/zabbix-server-mysql-4.0.1 zcat create.sql.gz | mysql -u zabbix -h Cloud_SQL_IP_ADDRESS -p zabbix
4. Zabbix-server設定
# vim /etc/zabbix/zabbix_server.conf # DBHost=localhost DBHost=CLOUD_SQL_IP_ADDRESS ... DBPassword=YOUR_DB_PASSWORD
起動しちゃいましょう。
systemctl start zabbix-server ; systemctl enable zabbix-server systemctl status zabbix-server
ついでにエージェントも起動。
systemctl start zabbix-agent ; systemctl enable zabbix-agent systemctl status zabbix-agent # 確認 zabbix_agentd -V
Zabbixサーバー自身からエージェントバージョンを確認します。
zabbix_get -s 127.0.0.1 -k agent.version
5. Zabbix-webの権限周り設定
必要なものインストール。
yum -y install nginx php-fpm wget
デフォルトapacheからnginx仕様に変更していきます。
ls -la /var/lib/php/ cd /var/lib/php/ chown -R nginx:nginx ./*
cd /etc/zabbix ls -ld web chown nginx:nginx web cd /var/www/html ln -s /usr/share/zabbix .
6. Nginx php-fpmの設定
nginx設定
# vim /etc/nginx/conf.d/zabbix.conf server { server_name YOUR_HOST_NAME; root /var/www/html/zabbix; index index.html index.php; access_log /var/log/nginx/zabbix/access.log main; error_log /var/log/nginx/zabbix/error.log error; location ~ \.php { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }
php-fpm設定
# vim /etc/php-fpm.d/www.conf -user = apache +user = nginx -group = apache +group = nginx
php.ini設定
※ やっておかないとこんな感じで後でZabbixに怒られます。
# vim /etc/php.ini -max_execution_time = 30 +max_execution_time = 300 -max_input_time = 60 +max_input_time = 300 -post_max_size = 8M +post_max_size = 32M +date.timezone = Asia/Tokyo
それぞれ起動します。
systemctl enable php-fpm ; systemctl start php-fpm systemctl enable nginx ; systemctl start nginx
ホスト設定してURL叩きます。
# vim /etc/hosts
ZABBIX_SEVER_IP_ADDRES YOUR_HOST_NAME
7. Zabbixの設定
初期ログインは、下記です。
Username:Admin Password:zabbix
何か問題があったらこちらを確認するだす!
tailf /var/log/zabbix/zabbix_server.log
設定を進めていきます。
パスワードと言語を更新しておきますー。
8. Grafana導入
次は、ZabbixのデータをGrafanaで見れるようにします。
参考URL:
Download Grafana
Grafana-Zabbix plugin Installation
とりあえず記載されている通り、Grafanaをインストール。
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.3.2-1.x86_64.rpm
yum localinstall -y grafana-5.3.2-1.x86_64.rpm
Grafana-Zabbixプラグインをインストールして起動させます。
grafana-cli plugins list-remote grafana-cli plugins install alexanderzobnin-zabbix-app systemctl restart grafana-server ; systemctl enable grafana-server
ZabbixでGrafanaグループを作成して権限を付与してユーザー設定します。
(この例だとZabbix serversしか閲覧付与していないけど。。)
Grafanaの設定をします。(ホスト名:3000です。)
初期ログインIDとパスワードは「admin」です。
参考URL:
Zabbixの監視情報をGrafanaでグラフィカルなダッシュボードにしてみた
なお、/grafanaでgrafanaログインに遷移したい場合は下記を追記します。
# /etc/nginx/conf.d/nginx.conf location /grafana/ { proxy_pass http://localhost:3000/; proxy_set_header Host $host; }
# vim /etc/grafana/grafana.ini [server] root_url = %(protocol)s://%(domain)s/grafana/
9. Zabbixエージェントの設定
監視対象となるサーバーで対応していきます。
Zabbix4のリポジトリを追加してエージェントをインストールします。
(これはサーバー構築するときにAnsibleで対応しちゃえばいいねー・・・)
参考URL:
VirtualBox Zabbix3.0 エージェント構築
rpm -ivh https://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-release-4.0-1.el7.noarch.rpm yum -y install zabbix-agent
エージェントの設定を編集していきます。
# vim /etc/zabbix/zabbix_agentd.conf # Zabbixサーバからのリモートコマンドを許可 - EnableRemoteCommands=0 + EnableRemoteCommands=1 # ZabbixサーバのIPアドレスを設定 - Server=127.0.0.1 + Server=ZABBIX_SEVER_IP - ServerActive=127.0.0.1 + ServerActive=ZABBIX_SEVER_IP
Zabbixエージェントの起動と自動起動設定
systemctl enable zabbix-agent ; systemctl start zabbix-agent # 確認 zabbix_agentd -V
Zabbixサーバ 疎通テスト
Zabbixサーバー側で下記コマンドを実行し、 Zabbixエージェント(192.168.33.10)のバージョンが表示されれば疎通OK。
zabbix_get -s 192.168.33.10 -k agent.version
Zabbixにホストを登録
10. Slackアラート
Slackにアラート流してみる。
参考URL:
[zabbix2][zabbix3]slackと連携してみたよ
Webサーバー停止する。
systemctl stop nginx
はーい、来ましたー。
参考:
Install and configure Zabbix server
Zabbix 4.0 インストールメモ(CentOS7 + MySQL 8.0)
CentOS 6.5でzabbix+nginx+php-fpm環境を構築してみる
Download Grafana
Grafana-Zabbix plugin Installation
Zabbixの監視情報をGrafanaでグラフィカルなダッシュボードにしてみた
VirtualBox Zabbix3.0 エージェント構築
[zabbix2][zabbix3]slackと連携してみたよ
How to proxy /grafana with nginx?
想ひ出20: GCP/Flask1.0/ローカルからCloudStorageにアップロードしてみる
おはようございます、moqrinです。
前々回に、GCEにデプロイしたFlaskのチュートリアルをCloudSQLと接続しました。
今度はCloudStorageを使ってローカルからUploadしてみようぜ、というお話です。
なお、基本的に過去の記事からの延長なのでご留意下さい。
やること
1. モデルを修正してマイグレーション
2. CloudStorageを作成してallUsersで閲覧OKにする
3. サービスアカウントキーを作成して手元に保存
4. 環境変数にサービスアカウントの情報を設定する
5. configにプロジェクトIDを設定
6. storage.pyを作成
7. Blueprintを修正
8. 画面フォームを修正
9. 確認
1. モデルを修正してマイグレーション
テーブルに画像保存するURLと画像名のColumnを追加します。
# app/models.py from app import db, login_manager from flask_login import UserMixin class User(UserMixin, db.Model): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(60), nullable=False, unique=True) password = db.Column(db.String(128), nullable=False) @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) class Post(db.Model): __tablename__ = 'post' id = db.Column(db.Integer, primary_key=True) author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) created = db.Column(db.TIMESTAMP, nullable=False) title = db.Column(db.String(200), nullable=False) body = db.Column(db.String(200), nullable=False) """ 追記 """ file_url = db.Column(db.TEXT(), nullable=False) filename = db.Column(db.String(200), nullable=False)
下記コマンドでマイグレーション対応完了です。
すでにテーブルがある場合はテーブル削除してから実行します。
flask db init flask db migrate flask db upgrade
2. CloudStorageを作成してallUsersで閲覧OKにする
Storageを作成して、allUsersで閲覧OKのメンバー追加します。
設定参考
3. サービスアカウントキーを作成して手元に保存
GCP外のアプリケーションからGoogle Cloud Storage API を有効化してアクセスするためには、サービスアカウントキーが必要となります。
Storageの読み書きできる権限で作成して、お手元にダウンロードします。
Cloud Storage Client Libraries
4. 環境変数にサービスアカウントキーを設定する
アクセス許可してもらうためにサービスアカウントキーをOSに設定しておきます。
export GOOGLE_APPLICATION_CREDENTIALS=path/to/ServiceAccountJsonKey
5. configにStorageが作成されたプロジェクトIDを設定
手っ取り早くこんな感じで設定しています。
# instance/config.py PROJECT_ID = 'moqrin-storage'
6. storage.pyを作成
ほぼチュートリアルそのままで利用しています。
# app/storage.py from __future__ import absolute_import import os os.getenv("GOOGLE_APPLICATION_CREDENTIALS") import datetime from google.cloud import storage import six from werkzeug import secure_filename from werkzeug.exceptions import BadRequest from app import ALLOWED_EXTENSIONS, PROJECT_ID, CLOUD_STORAGE_BUCKET def _get_storage_client(): return storage.Client( project=PROJECT_ID) def _check_extension(filename, allowed_extensions): if ('.' not in filename or filename.split('.').pop().lower() not in allowed_extensions): raise BadRequest( "{0} has an invalid name or extension".format(filename)) def _safe_filename(filename): filename = secure_filename(filename) date = datetime.datetime.utcnow().strftime("%Y-%m-%d-%H%M%S") basename, extension = filename.rsplit('.', 1) return "{0}-{1}.{2}".format(basename, date, extension) def upload_file(file_stream, filename, content_type): _check_extension(filename, ALLOWED_EXTENSIONS) filename = _safe_filename(filename) client = _get_storage_client() bucket = client.bucket(CLOUD_STORAGE_BUCKET) blob = bucket.blob(filename) blob.upload_from_string( file_stream, content_type=content_type) url = blob.public_url if isinstance(url, six.binary_type): url = url.decode('utf-8') print(url) return url
7. Blueprintを修正
下記の公式ドキュメントを参考にアップロード機能を実装していきます。
追記の部分を記載していきます。元はこちらです。
# app/__init__.py ... ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif']) PROJECT_ID = PROJECT_ID CLOUD_STORAGE_BUCKET = 'YOUR-BUCKET-NAME' ... def create_app(test_config=None): ... return app
# app/blog.py from flask import ( Blueprint, flash, g, redirect, render_template, request, url_for ) from app import storage from . import db, ALLOWED_EXTENSIONS ... bp = Blueprint('blog', __name__) def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS def upload_image_file(file): if not file: return None public_url = storage.upload_file( file.read(), file.filename, file.content_type ) return public_url ... @bp.route('/create', methods=('GET', 'POST')) @login_required def create(): if request.method == 'POST': title = request.form['title'] body = request.form['body'] error = None if not title: error = 'Title is required.' if 'img_file' not in request.files: flash('No file part') return redirect(request.url) file = request.files['img_file'] image_url = upload_image_file(request.files.get('img_file')) if error is not None: flash(error) else: post = Post(title=title, body=body, author_id=g.user.id, file_url=image_url, filename=file.filename ) ... @bp.route('/<int:id>/update', methods=('GET', 'POST')) @login_required def update(id): post = Post.query.get_or_404(id) if request.method == 'POST': title = request.form['title'] body = request.form['body'] error = None if not title: error = 'Title is required.' if error is not None: flash(error) if 'img_file' in request.files: file = request.files['img_file'] if file and allowed_file(file.filename): image_url = upload_image_file(request.files.get('img_file')) post.id = id post.title = title post.body = body post.file_url = image_url post.filename = file.filename db.session.commit() flash('Successfully edited the post.') else: post.id = id post.title = title post.body = body db.session.commit() ...
8. 画面フォームを修正
画像表示およびアップロードができるように修正します。
# app/templates/blog/index.html {% extends 'base.html' %} {% block header %} <h1>{% block title %}Posts{% endblock %}</h1> {% if current_user.is_authenticated %} <a class="action" href="{{ url_for('blog.create') }}">New</a> {% endif %} {% endblock %} {% block content %} {% for post in posts %} <article class="post"> <header> <div> <h1>{{ post.title }}</h1> <div class="about">by {{ post.username }} on {{ post.created.strftime('%Y-%m-%d') }}</div> </div> <a class="action" href="{{ url_for('blog.update', id=post.id) }}">Edit</a> </header> <p class="body">{{ post.body }}</p> <img src="{{ post.file_url }}" width="400" height="200"> </article> {% if not loop.last %} <hr> {% endif %} {% endfor %} {% endblock %}
# app/templates/blog/create.html {% extends 'base.html' %} {% block header %} <h1>{% block title %}New Post{% endblock %}</h1> {% endblock %} {% block content %} <form method="POST" enctype="multipart/form-data"> <label for="title">Title</label> <input name="title" id="title" value="{{ request.form['title'] }}" required> <label for="body">Body</label> <textarea name="body" id="body">{{ request.form['body'] }}</textarea> <input type="file" id="img_file" name="img_file"> <input type="submit" value="Save"> </form> {% endblock %}
# app/templates/blog/update.html {% extends 'base.html' %} {% block header %} <h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1> {% endblock %} {% block content %} <form method="post" enctype="multipart/form-data"> <label for="title">Title</label> <input name="title" id="title" value="{{ request.form['title'] or post['title'] }}" required> <label for="body">Body</label> <textarea name="body" id="body">{{ request.form['body'] or post['body'] }}</textarea> <img src="{{ post.file_url }}" width="400" height="200"> <input type="file" id="img_file" name="img_file"> <input type="submit" value="Save"> </form> <hr> <form action="{{ url_for('blog.delete', id=post['id']) }}" method="post"> <input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');"> </form> {% endblock %}
9. 確認
Google CloudのAPI Client libraryをインストール
pip install gcloud
起動。
export FLASK_APP=run export FLASK_ENV=development flask run
表示を確認して喜びます。
わーい。 でも...やっぱり何だか動作おせーー。。。w
参考:
Python での Cloud Storage の使用
Python Client for Google Cloud Storage