今回は、Alibaba Cloud Function Computeを使って、低コストでサーバーレスなWebサイトを構築していきます。
本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。
Alibaba Cloud Tech Share執筆者、Alberto Roura 。Tech Shareは、技術的な知識やベストプラクティスをクラウドコミュニティ内で共有することを奨励するAlibaba Cloudのインセンティブプログラムです。
DevOpsの観点から見ると、適切な責任の分離がなされた優れたアーキテクチャー・ソリューションの重要性は、あらゆるアプリケーションの長期的な成功のための基本となります。今日ご紹介するこのケースは、コンセプトを簡単に理解できるように、非常に単純化した例ですが、このトピックについて自信を持つようになると、自分自身でスケールアップするためのベースを設定します。Elastic Compute Service (ECS)、Server Load Balancer (SLB)、Virtual Private Cloud (VPC)を使用します。
コンテナとサービスの実行
今日の例のようにDockerを使う場合、コンテナごとに1つ以上の関数を実行してはいけません。コンテナを追加してもリソースのコストがかからないため、1つ以上の機能を実行してしまうとコンテナを使う意味がなくなってしまいます。私の経験では、DevOps のリードエンジニアとして、1 つのコンテナで複数の機能を管理しているスーパーバイザーがいるプロジェクトがあまりにも多く見られました。これは、トラッキング、デバッグ、水平スケールが非常に難しくなるため、アンチパターンと考えられています。私がプロセスではなく、関数という言葉を使っていることに注意してください。公式のDockerのドキュメントでは、1つの「プロセス」という表現から、代わりにコンテナごとに1つの「懸念事項」や「関数」を推奨するようになりました。
今日の例では、以下のようにWebサービスとアプリサービスを使って責任を分離する方法を示しています。
PHPアプリケーションを使用する際には、使用するウェブサーバを選択しなければなりません。mod_phpを使用することで、Apache自身がPHPコードを直接実行することができるので、Apacheはモジュールの面では優れています。これは、1つのコンテナだけを必要とし、アプリケーションを適切に追跡してデバッグすることができることを意味します。また、接続の同時実行性が高くないウェブサイトでの展開の複雑さを軽減するのにも役立ちます。
今日の私たちの目標は、拡張性の高いプロジェクトを構築することです。Tengineがここにあるので、Webサーバーをアプリケーションコードベース自体から分離する必要があるため、すべてを節約できます。このWebサーバーを使用すると非常に簡単になります。
Tengine とは?
Tengine は一言で言えば、超強力な Nginx です。このウェブサーバーは、Nginx の特徴である動的モジュールロード(再コンパイルせずにモジュールをロード)、バッファリングされていないリクエストプロキシ転送(貴重なディスク I/O を節約)、設定ファイルのための動的スクリプト言語(Lua)のサポートなど、いくつかの機能を備えています。Tengine の公式サイトで機能の全リストを見ることができます。
このウェブサーバーは2011年12月からオープンソースプロジェクトです。アリババのグループ会社である淘宝(タオバオ)チームによって作られ、それ以来、淘宝(タオバオ)やTmallのようなサイトで使用されており、内部的にはアリババクラウドで彼らのCDNのロードバランサーの重要な部分として使用されています。
ご覧のように、Tengineは世界で最も忙しいウェブサイトのいくつかでテストされています。
ファイルの準備
前述したように、Webサーバーをアプリケーションコードベースから分離し、非常にシンプルでスケーラブルなプロジェクトを構築することを計画しています。これは、ユーザーの要求をリッスンするWebサーバーとバックエンドとしてのPHPFPMインタープリターの2つのコンテナーを実行することで実現されます。これは「ベストプラクティス」タイプの記事であるため、サーバーロードバランサーを設定して、後に全体をスケーリングします。最後に、予測可能な方法でインフラストラクチャを作成、変更、および改善するために使用されるツールであるTerraformを使用してすべてを展開します。ワークステーションがAlibabaCloudと連携するようにセットアップする方法については、このビデオウェビナーをご覧ください。
docker-compose.yml
Docker Composeプロジェクトの成熟度を考えると、以下のようにdocker-compose.yml
ファイルを記述してこのアプリケーションをデプロイすることになります。
version: '3.7'
networks:
webappnet:
driver: bridge
services:
app:
image: php:7.3-fpm
container_name: fpm
networks:
- webappnet
restart: on-failure
volumes:
- ./index.php:/var/www/html/index.php
web:
image: roura/php:tengine-fpm
container_name: tengine
networks:
- webappnet
restart: on-failure
ports:
- 80:80
volumes:
- ./index.php:/var/www/html/index.php
ご覧のように、ネットワーク上ではアプリとウェブの2つのサービスが動作しています。ウェブサービスは、デフォルトではFPMが公開しているポート9000で、すべてのリクエストをアプリに渡します。将来的には、必要に応じて2つのサービスを独立して水平方向に拡張することができます。
index.php
アプリはシンプルなものになると言いましたが、ご覧のように、ファイルを持っているだけですが、PHPスクリプトがバックエンドでレンダリングされているかどうかをテストするには十分です。docker-compose.yml
の隣に以下の内容のindex.php
を作成します。
<?php phpinfo();
user-data.sh
このファイルは、ECSインスタンス内のすべてのサービスをブートストラップするためのメインテンプレートとして機能します。基本的にすべての依存関係をインストールし、コンテナを起動するための最小限のファイルを作成します。
# !/usr/bin/env bash
mkdir /var/docker
cat <<- 'EOF' > /var/docker/docker-compose.yml
${docker_compose}
EOF
cat <<- 'EOF' >/var/docker/index.php
${index_php}
EOF
apt-get update && apt-get install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
apt-get update && apt-get install -y docker-ce docker-compose
curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/bin/docker-compose
cd /var/docker && docker-compose up -d
main.tf
これはすべてをオーケストレーションするファイルです。新しいVPC、サーバロードバランサ、インターネットIPを持たないECS、セキュリティグループを作成し、ポート80のみにロックします。main.tf という名前のファイルは、先ほど作成した他のファイルの隣に置く必要があります。以下をご覧ください。
provider "alicloud" {}
variable "app_name" {
default = "webapp"
}
data "template_file" "docker_compose" {
template = "${file("docker-compose.yml")}"
}
data "template_file" "index_php" {
template = "${file("index.php")}"
}
data "template_file" "user_data" {
template = "${file("user-data.sh")}"
vars {
docker_compose = "${data.template_file.docker_compose.rendered}"
index_php = "${data.template_file.index_php.rendered}"
}
}
data "alicloud_images" "default" {
name_regex = "^ubuntu_16.*_64"
}
data "alicloud_instance_types" "default" {
instance_type_family = "ecs.n4"
cpu_core_count = 1
memory_size = 2
}
resource "alicloud_vpc" "main" {
cidr_block = "172.16.0.0/12"
}
data "alicloud_zones" "default" {
available_disk_category = "cloud_efficiency"
available_instance_type = "${data.alicloud_instance_types.default.instance_types.0.id}"
}
resource "alicloud_vswitch" "main" {
availability_zone = "${data.alicloud_zones.default.zones.0.id}"
cidr_block = "172.16.0.0/16"
vpc_id = "${alicloud_vpc.main.id}"
}
resource "alicloud_security_group" "group" {
name = "${var.app_name}-sg"
vpc_id = "${alicloud_vpc.main.id}"
}
resource "alicloud_security_group_rule" "allow_http" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "intranet"
policy = "accept"
port_range = "80/80"
priority = 1
security_group_id = "${alicloud_security_group.group.id}"
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_security_group_rule" "egress" {
type = "egress"
ip_protocol = "tcp"
nic_type = "intranet"
policy = "accept"
port_range = "80/80"
priority = 1
security_group_id = "${alicloud_security_group.group.id}"
cidr_ip = "0.0.0.0/0"
}
resource "alicloud_slb" "main" {
name = "${var.app_name}-slb"
vswitch_id = "${alicloud_vswitch.main.id}"
internet = true
}
resource "alicloud_slb_listener" "https" {
load_balancer_id = "${alicloud_slb.main.id}"
backend_port = 80
frontend_port = 80
health_check_connect_port = 80
bandwidth = -1
protocol = "http"
sticky_session = "on"
sticky_session_type = "insert"
cookie = "webappslbcookie"
cookie_timeout = 86400
}
resource "alicloud_slb_attachment" "main" {
load_balancer_id = "${alicloud_slb.main.id}"
instance_ids = [
"${alicloud_instance.webapp.id}"
]
}
resource "alicloud_instance" "webapp" {
instance_name = "${var.app_name}"
image_id = "${data.alicloud_images.default.images.0.image_id}"
instance_type = "${data.alicloud_instance_types.default.instance_types.0.id}"
vswitch_id = "${alicloud_vswitch.main.id}"
security_groups = [
"${alicloud_security_group.group.id}"
]
password = "Test1234!"
user_data = "${data.template_file.user_data.rendered}"
}
output "slb_ip" {
value = "${alicloud_slb.main.address}"
}
まとめる
全てのファイルが書き込まれたので、これで準備は完了です。terraform init && terraform apply
を実行して、すべてのインフラストラクチャリソースが作成されるのを待ちます。
すべてが終わると、slb_ip = 47.xx.xx.164
のような出力が出てきます。これがロードバランサーのパブリックIPです。それをコピーしてウェブブラウザに貼り付けてください。以下のスクリーンショットのような画面が表示されるはずです。
おめでとうございます!これでより良いクラウドアーキテクトになりました。
アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ