お久しぶりです。インフラソリューション部の”のなか”です。

今回は、AWS上で稼働している既存WordPressを更新する際にかかっているデプロイ時間を30分から5分に圧縮した話をしていきます。

インフラの管理やサーバーの設定等の様々な技術を使用して説明するため、前編と後編に分けて書いていきます。

最終的にはWordPressを自動でデプロイし、最新のAMIが作成され、Auto Scaling Group(以降ASG)の起動テンプレートのAMIが自動で更新されるようになります。

それではWordPressのデプロイ自動化の前編を始めていきましょう!

開発環境

– Terraform 1.3.3

  – OS(WSL2):Ubuntu 20.04.5 LTS

– Ansible

  – OS(EC2):Amazon Linux 2

  – MW:Nginx 1.22.1

  – MW:MariaDB 10.5.10

  – MW:PHP-FPM 7.4.33

  – ソフトウェア:WordPress 5.7.8

最終的に構築するシステム

前編に入る前に前編後編合わせた自動デプロイシステム全体の構成について説明します。

本ブログの目的は以下の自動デプロイシステムを構築することになります。

自動デプロイシステムは以下流れで処理されます。

①WordPressをGUI上で設定を変更し、EC2にSSH接続

CodeCommitにWordPressのソースコードをpush

CodePipelineを起動し、Sourceステージを開始し、Deployステージに遷移

④DeployステージでCodeDeployを使用してASGにWordPressをデプロイ

⑤InvokeステージでLambdaを実行

⑥InvokeステージでEC2 Image Builderを呼び出す

EC2 Image BuilderでAMIを作成

⑧ASGの起動テンプレートのAMIを自動更新

詳細は後編で説明しますが、自動デプロイシステムにはデプロイ後のAMI作成に時間がかかるという欠点とデプロイする度にAMIが作成されていく欠点があります。

先に改善案を書いておきますと、インスタンスのテストが不要であれば⑤~⑧を削除し、AWS Backupを使用する構成に変更する方が良いと思います。

自動デプロイシステムの構成図は以下の通りになります。

全体の説明についてはこれで終了し、前編の説明に入っていきます。

注意点

Ansibleこちらの記事こちらの記事で説明しているため省略

Terraformでリソース作成のためにアクセスキーを使用していますが、スイッチロールを使用するのがベストプラクティス

– VPC等のAWSの基本リソースに関して詳細な説明は省略

– バージョン等の環境による実行結果に差異がある

前提条件

– WSL2にaws cliのバージョン2.8.12とTerraformのバージョン1.3.3をインストール済み

– AWSのアカウントと作業用ユーザーのBlogUserを作成済み

– BlogUserでアクセスキーとシークレットアクセスキーを作成済み

– BlogUserに以下権限をアタッチ済み

  – IAMFullAccess

– 以下ポリシーをアタッチしたIAMロールのROLE_BLOGを作成済み

  – AmazonEC2FullAccess

  – AmazonSSMFullAccess

SSMのクイックセットアップを実施済み

– EC2にアクセスするためのキーペアを作成済み

用語説明

Terraformとは

まず初めにTerraformについて説明します。

Terraformとは[公式ドキュメントで以下のように説明されています。

HashiCorp Terraform is an infrastructure as code tool
that lets you define both cloud and on-prem resources
in human-readable configuration files that you can version, reuse, and share.
You can then use a consistent workflow to provision and manage
all of your infrastructure throughout its lifecycle.
Terraform can manage low-level components like compute, storage, and networking resources,
as well as high-level components like DNS entries and SaaS features.

説明を1行でまとめるとクラウドやオンプレミスのリソースを設定ファイルで定義するIaCツールで、インフラのプロビジョニングや管理が可能です。

今回はほぼ全てのAWSのリソースをTerraformで管理していきます。

WP-CLIとは

次にWP-CLIについて説明します。

WP-CLIは公式ドキュメントで以下のように説明されています。

WP-CLI は WordPress を管理するためのコマンドラインインターフェースです。

プラグインのアップデートやマルチサイトのセットアップなど、多くのことをブラウザなしで実行できます。

以上の説明からも分かりますが、WP-CLIはWordPress自体のアップデートやプラグインのインストール、テーマのインストール等がコマンドで操作可能なツールです。

前編のリソース作成手順

手順

1. Terraformで各リソースを作成し、AnsibleWP-CLIを使用してWordPressを構築

2. 構築したサーバーからAMIを作成

3. 作成したAMIから起動テンプレートを作成し、ASGを作成

前編の構成図と使用するAWSのリソース

構成図

使用するAWSのリソース

IAM

VPC

EC2

SSM

前編でのディレクトリ構成

Terraform

前編の`~/terraform-blog`は以下のディレクトリ構成になります。

.
├── .terraform
│   └── providers
│       └── registry.terraform.io
│           └── hashicorp
│               └── aws
│                   └── 4.57.1
│                       └── linux_amd64
│                           └── terraform-provider-aws_v4.57.1_x5
├── .terraform.lock.hcl
├── aws_asg.tf
├── aws_data.tf
├── aws_ec2.tf
├── aws_iam.tf
├── aws_vpc.tf
├── provider.tf
├── terraform.tfstate
└── terraform.tfstate.backup

Ansible

前編の`/etc/ansible`は以下のディレクトリ構成になります。

.
├── ansible.cfg
├── hosts
├── roles
│   ├── common
│   │   ├── files
│   │   │   └── chrony.conf
│   │   └── tasks
│   │       └── main.yml
│   ├── mariadb
│   │   ├── tasks
│   │   │   └── main.yml
│   │   └── vars
│   │       └── main.yml
│   ├── nginx
│   │   ├── files
│   │   │   └── nginx.conf
│   │   └── tasks
│   │       └── main.yml
│   ├── php
│   │   ├── files
│   │   │   └── www.conf
│   │   └── tasks
│   │       └── main.yml
│   └── wp
│       ├── tasks
│       │   └── main.yml
│       └── vars
│           └── main.yml
└── site.yml

構築

IAMを設定

1. AWSのコンソールにサインインし、BlogUserに以下ポリシーをアタッチしたBasic_Groupユーザーグループを作成

– AmazonEC2FullAccess

– AmazonSSMFullAccess

– AmazonVPCFullAccess

2. 作成したBasic_GroupをBlogUserに追加

3. ASG用のサービスロールを作成

TerraformでIAMを管理

1. `vi ~/.aws/credentials`でアクセスキーとシークレットアクセスキーを設定

```~/.aws/credentials
[BlogUser]
aws_access_key_id = "ここにアクセスキーを記載"
aws_secret_access_key = "ここにシークレットアクセスキーを記載"
```

2. WSL2に各リソースを作成するための作業用ディレクトリを作成

sudo apt -y update
mkdir ~/terraform-blog
cd ~/terraform-blog

3. `vi provider.tf`でプロバイダー情報を設定

– provider:AWS Provider

provider "aws" {
  region  = "ap-northeast-1"
  profile = "BlogUser"
}

4. `vi aws_data.tf`で外部参照をするためのデータを設定

Terraformのaws_iam_policy_documentはData Source: aws_iam_policy_documentを確認してください。

#----------------------------------------
# IAMポリシーを定義
#----------------------------------------
data "aws_iam_policy_document" "allow_asg" {
  statement {
    sid    = "EC2InstanceManagement"
    effect = "Allow"
    actions = [
      "ec2:AttachClassicLinkVpc",
      "ec2:CancelSpotInstanceRequests",
      "ec2:CreateFleet",
      "ec2:CreateTags",
      "ec2:DeleteTags",
      "ec2:Describe*",
      "ec2:DetachClassicLinkVpc",
      "ec2:ModifyInstanceAttribute",
      "ec2:RequestSpotInstances",
      "ec2:RunInstances",
      "ec2:StartInstances",
      "ec2:StopInstances",
      "ec2:TerminateInstances"
    ]
    resources = ["*"]
  }
  statement {
    sid    = "EC2InstanceProfileManagement"
    effect = "Allow"
    actions = [
      "iam:PassRole"
    ]
    resources = ["*"]
  }
  statement {
    sid    = "EC2SpotManagement"
    effect = "Allow"
    actions = [
      "iam:CreateServiceLinkedRole"
    ]
    resources = ["*"]
  }
  statement {
    sid    = "ELBManagement"
    effect = "Allow"
    actions = [
      "elasticloadbalancing:Register*",
      "elasticloadbalancing:Deregister*",
      "elasticloadbalancing:Describe*"
    ]
    resources = ["*"]
  }
  statement {
    sid    = "CWManagement"
    effect = "Allow"
    actions = [
      "cloudwatch:DeleteAlarms",
      "cloudwatch:DescribeAlarms",
      "cloudwatch:GetMetricData",
      "cloudwatch:PutMetricAlarm"
    ]
    resources = ["*"]
  }
  statement {
    sid    = "SNSManagement"
    effect = "Allow"
    actions = [
      "sns:Publish"
    ]
    resources = ["*"]
  }
  statement {
    sid    = "EventBridgeRuleManagement"
    effect = "Allow"
    actions = [
      "events:PutRule",
      "events:PutTargets",
      "events:RemoveTargets",
      "events:DeleteRule",
      "events:DescribeRule"
    ]
    resources = ["*"]
  }
  statement {
    sid    = "SystemsManagerParameterManagement"
    effect = "Allow"
    actions = [
      "ssm:GetParameters"
    ]
    resources = ["*"]
  }
  statement {
    sid    = "VpcLatticeManagement"
    effect = "Allow"
    actions = [
      "vpc-lattice:DeregisterTargets",
      "vpc-lattice:GetTargetGroup",
      "vpc-lattice:ListTargets",
      "vpc-lattice:ListTargetGroups",
      "vpc-lattice:RegisterTargets"
    ]
    resources = ["*"]
  }
}

5. `vi aws_iam.tf`でIAMのロールとサービスロールを設定

– IAMユーザー:Resource: aws_iam_user

– IAMポリシー:Resource: aws_iam_policy

– IAMロール:Resource: aws_iam_role

#----------------------------------------
# IAMユーザーを作成
#----------------------------------------
resource "aws_iam_user" "BlogUser" {
  name = "BlogUser"
  path = "/"
}

#----------------------------------------
# IAMポリシーを作成
#----------------------------------------
resource "aws_iam_policy" "AutoScalingServiceRolePolicy" {
  name   = "AutoScalingServiceRolePolicy"
  policy = data.aws_iam_policy_document.allow_asg.json
}

#----------------------------------------
# IAMロールを作成
#----------------------------------------
resource "aws_iam_role" "ROLE_BLOG" {
  name               = "ROLE_BLOG"
  assume_role_policy = "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}"
  description        = "Allows EC2 instances to call AWS services on your behalf."
  path               = "/"
  managed_policy_arns = [
    "arn:aws:iam::aws:policy/AmazonEC2FullAccess",
    "arn:aws:iam::aws:policy/AmazonSSMFullAccess",
  ]
  tags = {
    Name = "ROLE_BLOG"
  }
}

#----------------------------------------
# IAMロールを作成
#----------------------------------------
resource "aws_iam_role" "AWSServiceRoleForAutoScaling" {
  name               = "AWSServiceRoleForAutoScaling"
  assume_role_policy = "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"autoscaling.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}"
  description        = "Default Service-Linked Role enables access to AWS Services and Resources used or managed by Auto Scaling"
  path               = "/aws-service-role/autoscaling.amazonaws.com/"
  managed_policy_arns = [
    "arn:aws:iam::aws:policy/aws-service-role/AutoScalingServiceRolePolicy"
  ]
  tags = {
    Name = "AWSServiceRoleForAutoScaling"
  }
}

6. 作成済みのユーザーとロールとサービスロールをインポート

– initコマンド:Command: init

– importコマンド:[Command: import

terraform init
terraform import aws_iam_user.BlogUser BlogUser
terraform import aws_iam_role.ROLE_BLOG ROLE_BLOG
terraform import aws_iam_role.AWSServiceRoleForAutoScaling AWSServiceRoleForAutoScaling

7. IAMを作成して管理

– planコマンドはCommand: plan

– importコマンドはCommand: apply

※以降はTerraformの各コマンドの説明を省略

terraform plan
terraform apply

Terraformでネットワークを構築

1. セキュリティグループを作成するためにGIP

2. `vi aws_vpc.tf`でネットワークを設定

– VPC:Resource: aws_vpc

– サブネット:Resource: aws_subnet

– インターネットゲートウェイ:Resource: aws_internet_gateway

– ルートテーブル:Resource: aws_route_table

– ルートテーブルの紐づけ:Resource: aws_route_table_association

– セキュリティグループ:Resource: aws_security_group

#----------------------------------------
# VPCの作成
#----------------------------------------
resource "aws_vpc" "vpc_blog" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = false
  tags = {
    Name = "vpc_blog"
  }
}

#----------------------------------------
# パブリックサブネット1aの作成
#----------------------------------------
resource "aws_subnet" "pub_subnet1a_blog" {
  vpc_id                  = aws_vpc.vpc_blog.id
  cidr_block              = "10.0.0.0/28"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = true
  tags = {
    Name = "pub_subnet1a_blog"
  }
}

#----------------------------------------
# パブリックサブネット1cの作成
#----------------------------------------
resource "aws_subnet" "pub_subnet1c_blog" {
  vpc_id                  = aws_vpc.vpc_blog.id
  cidr_block              = "10.0.0.16/28"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = true
  tags = {
    Name = "pub_subnet1c_blog"
  }
}

#----------------------------------------
# インターネットゲートウェイの作成
#----------------------------------------
resource "aws_internet_gateway" "igw_blog" {
  vpc_id = aws_vpc.vpc_blog.id

  tags = {
    Name = "igw_blog"
  }
}

#----------------------------------------
# ルートテーブルの作成
#----------------------------------------
resource "aws_route_table" "rtb_blog" {
  vpc_id = aws_vpc.vpc_blog.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw_blog.id
  }

  tags = {
    Name = "rtb_blog"
  }
}

#----------------------------------------
# パブリックサブネット1aにルートテーブルを紐づけ
#----------------------------------------
resource "aws_route_table_association" "rt_assoc_1a" {
  subnet_id      = aws_subnet.pub_subnet1a_blog.id
  route_table_id = aws_route_table.rtb_blog.id
}

#----------------------------------------
# パブリックサブネット1cにルートテーブルを紐づけ
#----------------------------------------
resource "aws_route_table_association" "rt_assoc_1c" {
  subnet_id      = aws_subnet.pub_subnet1c_blog.id
  route_table_id = aws_route_table.rtb_blog.id
}

#----------------------------------------
# セキュリティグループの作成
#----------------------------------------
resource "aws_security_group" "sg_blog" {
  name   = "sg_blog"
  vpc_id = aws_vpc.vpc_blog.id
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["(調べたGIPをここに記載)/32"]
  }
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["(調べたGIPをここに記載)/32"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "sg_blog"
  }
}

3. ネットワークを構築して管理

terraform plan
terraform apply

4. 構成図のネットワークが構築されたことを確認

Terraformでサーバーを構築

1. `vi aws_ec2.tf`でサーバーを設定

– EC2:Resource: aws_instance

#----------------------------------------
# EC2インスタンスを作成
#----------------------------------------
resource "aws_instance" "blog_wp01" {
  ami                    = "ami-0329eac6c5240c99d"
  instance_type          = "t2.micro"
  subnet_id              = aws_subnet.pub_subnet1a_blog.id
  key_name               = "(作成済みのキーペア名をここに記載)"
  vpc_security_group_ids = [aws_security_group.sg_blog.id]
  private_ip             = "10.0.0.10"
  iam_instance_profile   = "ROLE_BLOG"
  tags = {
    Name   = "blog_wp01"
    System = "blog"
  }
}

2. サーバーを構築して管理

terraform plan
terraform apply

3. 構成図のサーバーが構築されていることを確認

4. サーバーにセッションマネージャーで接続出来ることを確認

※接続出来ない場合は前提条件にある[SSMのクイックセットアップが実施済み](#前提条件)か確認してください。

AnsibleでWordPressを構築

1. 構築したサーバーにSSHし、以下コマンドでサーバーをアップデートし、パッケージ等をインストール

sudo yum -y update
sudo su -
amazon-linux-extras install -y ansible2
ansible-galaxy collection install community.mysql

2. Ansibleのディレクトリを作成

cd /etc/ansible
mkdir -p roles/common/files
mkdir roles/common/tasks
mkdir -p roles/nginx/files
mkdir roles/nginx/tasks
mkdir -p roles/mariadb/vars
mkdir roles/mariadb/tasks
mkdir -p roles/php/files
mkdir roles/php/tasks
mkdir -p roles/wp/vars
mkdir roles/wp/tasks

3. playbookをローカル実行する設定をhostsファイルに追記

echo [all] >> hosts
echo localhost ansible_connection=local >> hosts

4. `vi site.yml`で各playbookを呼び出すplaybookを作成

- hosts: all
  become: yes
  gather_facts: no
  name: common settings
  roles:
    - common
    - nginx
    - mariadb
    - php
    - wp

5. `vi roles/common/files/chrony.conf`で時刻の設定ファイルを作成

# Use NTP servers from DHCP.
sourcedir /run/chrony-dhcp

# Include configuration found in /etc/chrony.d/*.conf
confdir /etc/chrony.d

# Use NTP sources found in /etc/chrony.d/*.sources
sourcedir /etc/chrony.d

# Record the rate at which the system clock gains/losses time.
driftfile /var/lib/chrony/drift

# Allow the system clock to be stepped in the first three updates
# if its offset is larger than 1 second.
makestep 1.0 3

# Enable kernel synchronization of the real-time clock (RTC).
rtcsync

# Specify file containing keys for NTP authentication.
keyfile /etc/chrony.keys

# Specify directory for log files.
logdir /var/log/chrony

# Select which information is logged.
log measurements statistics tracking

server 0.jp.pool.ntp.org iburst
server 1.jp.pool.ntp.org iburst
server 2.jp.pool.ntp.org iburst
server 3.jp.pool.ntp.org iburst

6. `vi roles/nginx/files/nginx.conf`でWEBサーバーの設定ファイルを作成

# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        #root         /usr/share/nginx/html;
        root         /var/www/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        error_page 404 /404.html;
        location = /404.html {
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }
    }
}

7. `vi roles/php/files/www.conf`でphp-fpmの設定ファイルを作成


; Start a new pool named 'www'.
; the variable $pool can we used in any directive and will be replaced by the
; pool name ('www' here)
[www]

; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
; RPM: apache user chosen to provide access to the same directories as httpd
;user = apache
user = nginx
; RPM: Keep a group allowed to write in log dir.
;group = apache
group = nginx

; The address on which to accept FastCGI requests.
; Valid syntaxes are:
;   'ip.add.re.ss:port'    - to listen on a TCP socket to a specific IPv4 address on
;                            a specific port;
;   '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
;                            a specific port;
;   'port'                 - to listen on a TCP socket to all addresses
;                            (IPv6 and IPv4-mapped) on a specific port;
;   '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
listen = /run/php-fpm/www.sock

; Set permissions for unix socket, if one is used. In Linux, read/write
; permissions must be set in order to allow connections from a web server.
; Default Values: user and group are set as the running user
;                 mode is set to 0660
;listen.owner = nobody
listen.owner = nginx
;listen.group = nobody
listen.group = nginx
;listen.mode = 0660

; When POSIX Access Control Lists are supported you can set them using
; these options, value is a comma separated list of user/group names.
; When set, listen.owner and listen.group are ignored
listen.acl_users = apache,nginx
;listen.acl_groups =

; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect.
; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original
; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address
; must be separated by a comma. If this value is left blank, connections will be
; accepted from any ip address.
; Default Value: any
listen.allowed_clients = 127.0.0.1

; Choose how the process manager will control the number of child processes.
; Possible Values:
;   static  - a fixed number (pm.max_children) of child processes;
;   dynamic - the number of child processes are set dynamically based on the
;             following directives. With this process management, there will be
;             always at least 1 children.
;             pm.max_children      - the maximum number of children that can
;                                    be alive at the same time.
;             pm.start_servers     - the number of children created on startup.
;             pm.min_spare_servers - the minimum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is less than this
;                                    number then some children will be created.
;             pm.max_spare_servers - the maximum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is greater than this
;                                    number then some children will be killed.
;  ondemand - no children are created at startup. Children will be forked when
;             new requests will connect. The following parameter are used:
;             pm.max_children           - the maximum number of children that
;                                         can be alive at the same time.
;             pm.process_idle_timeout   - The number of seconds after which
;                                         an idle process will be killed.
; Note: This value is mandatory.
pm = dynamic

; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI. The below defaults are based on a server without much resources. Don't
; forget to tweak pm.* to fit your needs.
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
; Note: This value is mandatory.
pm.max_children = 50

; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.start_servers = 5

; The desired minimum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.min_spare_servers = 5

; The desired maximum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.max_spare_servers = 35

; The log file for slow requests
; Default Value: not set
; Note: slowlog is mandatory if request_slowlog_timeout is set
slowlog = /var/log/php-fpm/www-slow.log

; Default Value: nothing is defined by default except the values in php.ini and
;                specified at startup with the -d argument
;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
;php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 128M

; Set the following data paths to directories owned by the FPM process user.
;
; Do not change the ownership of existing system directories, if the process
; user does not have write permission, create dedicated directories for this
; purpose.
;
; See warning about choosing the location of these directories on your system
; at http://php.net/session.save-path
php_value[session.save_handler] = files
php_value[session.save_path]    = /var/lib/php/session
php_value[soap.wsdl_cache_dir]  = /var/lib/php/wsdlcache
;php_value[opcache.file_cache]  = /var/lib/php/opcache

8. `vi roles/common/tasks/main.yml`で共通の設定を作成

- name: set locale
  shell: "localectl set-locale LANG=ja_JP.UTF-8 LANGUAGE=\"ja_JP:ja\""

- name: set timezone
  timezone:
    name: Asia/Tokyo

- name: copy chrony.conf
  copy:
    src: ../files/chrony.conf
    dest: /etc/chrony

9. `vi roles/nginx/tasks/main.yml`でwebサーバーの設定を作成

- name: Enable amzn2extra-nginx1 repository
  shell: amazon-linux-extras enable nginx1
  changed_when: false

- name: Install Nginx packages from amazon-linux-extras
  when: not ansible_check_mode
  yum:
    name: nginx
    state: present

- name: append nginx group to ec2-user user
  when: not ansible_check_mode
  user:
    name: ec2-user
    append: yes
    groups: ec2-user,nginx

- name: change owner and mode
  file:
    path: /var/www/html
    state: directory
    recurse: yes
    group: nginx
    owner: ec2-user
    mode: 0775

- name: copy nginx
  copy:
    src: ../files/nginx.conf
    dest: /etc/nginx

- name: chown nginx
  when: not ansible_check_mode
  file:
    path: /etc/nginx/nginx.conf
    state: file
    group: root
    owner: root
    mode: 0644

- name: restart nginx
  when: not ansible_check_mode
  service:
    name: nginx
    state: restarted
    enabled: yes

10. `vi roles/mariadb/vars/main.yml`でdbサーバー用の変数の設定を作成

db_name: wordpress-db
db_user_name: wordpress-user
db_user_password: WPWP@123

11. `vi roles/mariadb/tasks/main.yml`でdbサーバーの設定を作成

- name: Enable amzn2extra-mariadb10.5 repository
  when: not ansible_check_mode
  shell: amazon-linux-extras enable mariadb10.5
  changed_when: false

- name: Install MariaDB packages from amazon-linux-extras
  yum:
    name: 
      - mariadb
      - MySQL-python
    state: present

- name: install PyMySQL
  pip:
    name: PyMySQL
    executable: pip3

- name: restart mariadb
  when: not ansible_check_mode
  service:
    name: mariadb
    state: restarted
    enabled: yes

- name: Create Database
  when: not ansible_check_mode
  mysql_db:
    login_unix_socket: /var/lib/mysql/mysql.sock
    login_user: root
    login_password: ""
    name: "{{ db_name }}"
    state: present
    encoding: utf8mb4
    collation: utf8mb4_general_ci

- name: Create Database User
  when: not ansible_check_mode
  mysql_user:
    login_unix_socket: /var/lib/mysql/mysql.sock
    login_user: root
    login_password: ""
    name: "{{ db_user_name }}"
    password: "{{ db_user_password }}"
    host: "localhost"
    priv: "{{ db_name }}.*:ALL,GRANT"
    state: present

- name: restart mariadb
  when: not ansible_check_mode
  service:
    name: mariadb
    state: restarted
    enabled: yes

12. `vi roles/php/tasks/main.yml`でapサーバーの設定を作成

- name: Enable amzn2extra-php7.4 repository
  when: not ansible_check_mode
  shell: amazon-linux-extras enable php7.4
  changed_when: false

- name: Install PHP packages from amazon-linux-extras
  yum:
    name:
      - php
      - php-pdo
      - php-mysqlnd
      - php-cli
      - php-json
      - php-gd
      - php-mbstring
      - php-xml
      - php-fpm
    state: present

- name: copy
  copy:
    src: ../files/www.conf
    dest: /etc/php-fpm.d/www.conf

- name: restart php-fpm
  when: not ansible_check_mode
  service:
    name: php-fpm
    state: restarted
    enabled: yes

13. `vi roles/wp/vars/main.yml`でdbサーバー用の変数の設定を作成

db_name: wordpress-db
db_user_name: wordpress-user
db_user_password: WPWP@123
title: blogtest
admin_user: bloguser
admin_password: b10Gb10G
admin_email: blogtest@example.com

14. `vi roles/wp/tasks/main.yml`でapサーバーの設定を作成

- name: Get wp-cli
  get_url:
    url: https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
    dest: /usr/local/bin/wp
    mode: 0755

- name: check file index.php
  stat:
    path: /var/www/html/index.php
  register: index_php

- name: wp download
  when: not index_php.stat.exists
  shell: "wp core download --locale=ja --path=/var/www/html --version=5.7.8"

- name: check file wp-config.php
  stat:
    path: /var/www/html/wp-config.php
  register: wpconfig_php

- name: wp config create
  when: not wpconfig_php.stat.exists
  shell: "wp config create --dbname={{ db_name }} --dbuser={{ db_user_name }} --dbpass={{ db_user_password }} --path=/var/www/html"

- name: wp install
  shell: "wp core install --url=http://$(curl inet-ip.info) --title={{ title }} --admin_user={{ admin_user }} --admin_password={{ admin_password }} --admin_email={{ admin_email }} --path=/var/www/html"

- name: restart nginx
  when: not ansible_check_mode
  service:
    name: nginx
    state: restarted
    enabled: yes

15. Ansibleのplaybookの作成が完了した`/etc/ansible`のディレクトリ構成

.
├── ansible.cfg
├── hosts
├── roles
│   ├── common
│   │   ├── files
│   │   │   └── chrony.conf
│   │   └── tasks
│   │       └── main.yml
│   ├── mariadb
│   │   ├── tasks
│   │   │   └── main.yml
│   │   └── vars
│   │       └── main.yml
│   ├── nginx
│   │   ├── files
│   │   │   └── nginx.conf
│   │   └── tasks
│   │       └── main.yml
│   ├── php
│   │   ├── files
│   │   │   └── www.conf
│   │   └── tasks
│   │       └── main.yml
│   └── wp
│       ├── tasks
│       │   └── main.yml
│       └── vars
│           └── main.yml
└── site.yml

playbookを実行

1. playbookのdry runを実行した後、playbookを実行

ansible-playbook site.yml --check
ansible-playbook site.yml

2. 構築したサーバーにwebでアクセスしてWordPressの初期画面が表示されることを確認

※EC2の停止でパブリックIPが変わり、アクセス出来なくなる問題が発生した場合は以下コマンドを実行すれば解消します。

wp option update home http://$(curl inet-ip.info) --path=/var/www/html

3. WordPressの管理画面にログイン出来ることを確認

– ユーザー名:bloguser

– パスワード:b10Gb10G

※EC2を停止する等でパブリックIPが変わり、アクセス出来ない等の問題が発生した場合は以下コマンドを実行すれば解消します。

wp option update siteurl http://$(curl inet-ip.info) --path=/var/www/html

TerraformでASGを作成

1. `vi aws_asg.tf`でEC2のAMIを作成し、AMIからASGを作成するtfファイルを作成

– AMI:Resource: aws_ami_from_instance

– 起動テンプレート:Resource: aws_launch_template

– ASG:Resource: aws_autoscaling_group

#----------------------------------------
# EC2インスタンスからAMIを作成
#----------------------------------------
resource "aws_ami_from_instance" "BlogAMI" {
  name               = "BlogAMI"
  source_instance_id = aws_instance.blog_wp01.id
  tags = {
    Name   = "BlogAMI"
  }
}

#----------------------------------------
# AMIから起動テンプレートを作成
#----------------------------------------
resource "aws_launch_template" "BlogTemplate" {
  name          = "BlogTemplate"
  image_id      = aws_ami_from_instance.BlogAMI.id
  instance_type = "t2.micro"
  key_name      = "KEY_BlogSample"
  user_data = base64encode(
    <<-EOF
      #!/bin/bash
      wp option update home http://$(curl inet-ip.info) --path=/var/www/html
      wp option update siteurl http://$(curl inet-ip.info) --path=/var/www/html
    EOF
  )
  network_interfaces {
    security_groups = [aws_security_group.sg_blog.id]
    subnet_id       = aws_subnet.pub_subnet1a_blog.id
  }
  iam_instance_profile {
    arn = "arn:aws:iam::596526128538:instance-profile/ROLE_BLOG"
  }
  tags = {
    Name      = "BlogTemplate"
  }
}

#----------------------------------------
# 起動テンプレートからASGを作成
#----------------------------------------
resource "aws_autoscaling_group" "BlogASG" {
  name                    = "BlogASG"
  protect_from_scale_in   = true
  service_linked_role_arn = aws_iam_role.AWSServiceRoleForAutoScaling.arn
  vpc_zone_identifier     = [aws_subnet.pub_subnet1a_blog.id]
  max_size                = 1
  min_size                = 1
  desired_capacity        = 1
  launch_template {
    name    = aws_launch_template.BlogTemplate.name
    version = "$Latest"
  }
  tag {
    key                 = "Name"
    propagate_at_launch = true
    value               = "BlogASG"
  }
}

2. ASGを作成して管理

terraform plan
terraform apply

3. 構成図のASGが作成されていることを確認

4. 作成したterraformのディレクトリ構成

.
├── .terraform
│   └── providers
│       └── registry.terraform.io
│           └── hashicorp
│               └── aws
│                   └── 4.57.1
│                       └── linux_amd64
│                           └── terraform-provider-aws_v4.57.1_x5
├── .terraform.lock.hcl
├── aws_asg.tf
├── aws_data.tf
├── aws_ec2.tf
├── aws_iam.tf
├── aws_vpc.tf
├── provider.tf
├── terraform.tfstate
└── terraform.tfstate.backup

改善点

今回はインフラを管理するツールとして個人的に使いやすいTerraformを使用しましたが、普段からコードをゴリゴリ書いている人だと知見を活用できるためPulumiを使用した方が良いと思います。またAWSのサービスで統一したい場合はCloudFormationを使用しても良いです。

さらにサーバーの初期設定にAnsibleを使用していますが、こちらもAWSのサービスで統一したい場合はOpsWorksを使用しても良いです。その場合はAnsibleは使用できないためChefPuppetを使用することになります。

またTerraformで一部のリソースを作成済みのものをインポートしていますが、実際の業務では手動で設定の作成や変更をするのはオススメしません。リソースを追加するにはtfファイルを書き換えるというルールを作るのが良いです。

所感

今回初めてWP-CLIを使用しましたが、WordPressをコマンドでダウンロードやインストールをしたり、GIPがインストール時と変わってもコマンドで設定を修正可能なため非常に便利でした。

前編ではインフラとWordPressの構築まで行いましたが、後編ではEC2 Image Builderでのゴールデンイメージ作成やCodeDeployを使用した自動デプロイを行います。

SHARE

  • facebook
  • twitter

SQRIPTER

AGEST Engineers

AGEST

記事一覧

AGESTのエンジニアが情報発信してます!

株式会社AGEST

Sqriptsはシステム開発における品質(Quality)を中心に、エンジニアが”理解しやすい”Scriptに変換して情報発信するメディアです

  • 新規登録/ログイン
  • 株式会社AGEST