同じECSインスタンスを使用して複数のウェブサイトをホスティングするDevOpsの方法


このチュートリアルでは、Docker Composeとリバースプロキシを使って、同じECSインスタンスを使って2つ以上のWebサイトを実行する方法を学びます。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです 。

なぜこのアプローチなのか?

ほとんどの場合、アリババクラウドECSインスタンスを取得したときには、1つのウェブサイトやアプリケーションにのみ使用します。しかし、家族と写真を共有したり、新しいブログを立ち上げたり、基本的にはコンテンツをホストして提供するために別のECSインスタンスを必要とするような、他のことに使用する必要がある場合もあります。このソリューションは、安定性のために推奨されていますが、時にはやりすぎてしまうこともあります。静的なサイトや小さなブログを運営するために、まったく新しいインスタンスを必要とすることはありません。代わりに、同じマシンでそれらをホストし、リソースを共有することでコストを削減することができます。

サーバーが複数のウェブサイトにサービスを提供する可能性があることをすでに知っていて、ECSインスタンスを最大限に活用したい場合は、1つの単一インスタンスECSで異なるコンテンツを処理するための複数のソリューションがあります。

1、サブフォルダ ("example.com/web1", "example.com/web2", "example.com/web3")
2、ポートベースの仮想ホスティング ("example.com:80", "example.com:8080", "example.com:8181")
3、サブドメイン(「web1.example.com」、「web2.example.com」、「web3.example.com
4、名前ベースのバーチャルホスティング ("web1.com", "web2.com", "web3.com")

名前ベースのバーチャルホスティング

それはあなたのサイトのためのよりよい名前を使用するのに役立ちますので、ここでは最高の(そしてより優雅な)解決は、名前ベースの仮想ホスティングを使用することです。マイクロソフトとグーグルの両方が私のサーバー上で自分のウェブサイトをホストすることを決定したことを一瞬想像してみてください。最初のステップは、「microsoft.com」と「google.com」の両方のDNS「A」レコードを更新して、ECSインスタンスのパブリックIPを指すようにすることです。

そして、ここで質問が出てきます。両方のウェブサイトの訪問者が同じIPに指示されている場合、どのようにしてそれぞれのリクエストを提供するページを区別するのでしょうか?ここで、HTTP ヘッダーが来ます。これは HTTP トランザクションのパラメータを定義します。以下の例を見てみましょう。

GET / HTTP/1.1
Host: microsoft.com
Connection: keep-alive
Cache-Control: no-cache
User-Agent: Chrome/66.0.3359.139
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-AU

ご覧のように、このHTTPリクエストのHostパラメータ(HTTP/1.1以降は必須)は "microsoft.com "を要求しています。この情報があれば、私のサーバーはどのページにサービスを提供するかを知っています。この場合、(GETリクエストによると)microsoft.comのURIです。

幸いなことに、HTTP/0.9バージョンを使用しているWebブラウザは残っていないので、リクエストはGETの部分だけをカバーし、ホストについては他には何もありません。

最も一般的には、ポートベース名前ベースバーチャルホスティングの両方は、 sites-available という名前のフォルダ内のホスト上の設定ファイルを使用して達成され、 sites-enabled でそれらをリンクしています。これらの設定ファイルは、どのようなドメイン名が使用されているか(例えば、web1.com)とウェブサイトがディスク(例えば、/var/www/html/web1.com/)に格納されている場所を記述します。

しかし、セキュリティ上の理由から、このチュートリアルではこのアプローチを使用しません。名前ベースの仮想ホストを展開するためにこのチュートリアルで使用しようとしているアプローチは、リクエストを管理するためにコンテナ化とリバースプロキシを使用しています。私にとっては、複雑さと DevOps のベストプラクティスを満たすためのステップアップです。

仮想化・コンテナ化

今日では、Dockerなどのソフトウェアは、コンテナ化とも呼ばれるオペレーティングシステムレベルの仮想化を実行しており、実行中のプログラムから見ると、実際のコンピュータのように見えます。これを使用する利点は、異なるコンテナで動作するサービス間の分離が可能になることで、あるコンテナで動作しているウェブサイトは、別のコンテナで動作しているウェブサイトとは(デフォルトでは)一切の接続を持ちません。

このようにして、複数のウェブサイトは、1つのECSインスタンス上で同時にコンテナ(オペレーティングシステム)で実行することができ、完全にお互いを認識していないことになります。彼らはホストで直接実行している場合と同じようにポート80を公開することさえあります。そして、Dockerはホスト内のランダムな利用可能なポートをゲートウェイとして割り当てます。

逆プロキシ

そして、おっしゃる通り、コンテナ化自体では、各サイトの訪問者に適切なサイトを提供するためのHTTPリクエストを処理するという問題はまだ解決していません。そこで必要になるのがリバースプロキシという、コンテナとしても動作し、リクエストをインターセプトして正しい場所に送るサービスで、バーチャルホスティングを扱うためのメインの部分になります。

次の図は、リバースプロキシがインターネットからのリクエストを受け取り、内部ネットワーク内のサーバに転送する様子を示しています。このプロキシにリクエストを出している訪問者は気づかないかもしれません。

私のお気に入りの解決策は、開発者の "jwilder "のオリジナルプロジェクトから "neilpang "が作ったフォークであるneilpang/nginx-proxyです。このフォークには “acme.sh "も含まれており、SSLを取得するためのプロセスを自動化しています。これは、あなたのサイトを保護し、同時に安全にするための本当に良いリバースプロキシであることがわかりました。

公式READMEによると、このnginx-proxyは「nginxとdocker-genを実行しているコンテナをセットアップします。これは nginx のリバースプロキシ設定を生成し、コンテナの起動時と停止時に nginx をリロードします。」つまり、基本的にはホスト上で稼働しているすべてのコンテナを追跡し、コンテナが追加されたときにリアルタイムでルールを作成します。

すべてのパーツをまとめる

OK! これでDocker上で動作し、リバースプロキシを持ち、少なくとも2つのウェブサイトが動作するような名前ベースのバーチャルホストソリューションが必要だということがわかりました。このハウツーのタイトルにあるように、マルチコンテナアプリケーションを定義して実行するための素晴らしいツールであるDocker Composeを使ってすべてをラップします。

docker-compose.yml設定ファイルの各コンテナは “service "と呼ばれ、YAML形式でサービス配列に入ります。このファイルのサービスは以下のようになります。

1、proxy
2、web1 (web1.com)
3、web2 (web2.com)
シンプルにするために、私は両方のウェブサービスでApache(php:7.2-apache)とPHP 7.2の標準的で修正されていない公式イメージを使用しています。”web1 "と “web2 "は、したがって、Apacheからの "It works!"という一般的なメッセージが表示されます。これは、コンテナにコマンドラインで docker exec -it web1 bash と入力することで変更することができます。

ECS上での実行

ホストに直接接続

すでにインスタンスを持っている場合は、公式のドキュメントに従ってDockerをインストールし、以下のような内容の「docker-compose.yml」というファイルを作成して、通常のDocker Composeプロジェクトのように実行してください。

docker-compose.yml

version: '3.6'

services:
  proxy:
    image: neilpang/nginx-proxy
    network_mode: bridge
    container_name: proxy
    restart: on-failure
    ports:
      - "80:80"
      - "443:443"
    environment:
      - ENABLE_IPV6=true
    volumes:
      - ./certs:/etc/nginx/certs
      - /var/run/docker.sock:/tmp/docker.sock:ro
  web1:
    image: php:7.2-apache
    network_mode: bridge
    container_name: web1
    restart: on-failure
    environment:
      - VIRTUAL_HOST=web1.com
  web2:
    image: php:7.2-apache
    network_mode: bridge
    container_name: web2
    restart: on-failure
    environment:
      - VIRTUAL_HOST=web2.com

Terraformで

もしまだECSインスタンスを持っていないのであれば、Alibaba Cloudのサポートが充実しているので、Terraformでやることをおすすめします。Terraform for Alicloudをまずローカルマシンで稼働させる方法については、こちらの記事をチェックしてみてください。

その記事の指示に従ってTerraformを動作させたら、いよいよmain.tfとuser-data.shを作成します。

main.tf

variable "access_key" {
  type = "string"
  default = "XXXXX"
}
variable "secret_key" {
  type = "string"
  default = "XXXXX"
}
variable "region" {
  type = "string"
  default = "ap-southeast-2"
}
variable "vswitch" {
  type = "string"
  default = "XXX-XXXXX"
}
variable "sgroups" {
  type = "list"
  default = [
    "XX-XXXXX"
  ]
}
variable "name" {
  type = "string"
  default = "multi-tenant"
}
variable "password" {
  type = "string"
  default = "Test1234!"
}

provider "alicloud" {
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
  region = "${var.region}"
}

data "alicloud_images" "search" {
  name_regex = "^ubuntu_16.*_64"
}
data "alicloud_instance_types" "default" {
  instance_type_family = "ecs.xn4"
  cpu_core_count = 1
  memory_size = 1
}
data "template_file" "user_data" {
  template = "${file("user-data.sh")}"
}

resource "alicloud_instance" "web" {
  instance_name = "${var.name}"
  image_id = "${data.alicloud_images.search.images.0.image_id}"
  instance_type = "${data.alicloud_instance_types.default.instance_types.0.id}"

  vswitch_id = "${var.vswitch}"
  security_groups = "${var.sgroups}"
  internet_max_bandwidth_out = 100

  password = "${var.password}"

  user_data = "${data.template_file.user_data.template}"
}

output "ip" {
  value = "${alicloud_instance.web.public_ip}"
}

user-data.sh

#!/bin/bash -v

# Create docker-compose.yml
cat <<- 'EOF' > /opt/docker-compose.yml
version: '3.6'
services:
  proxy:
    image: neilpang/nginx-proxy
    network_mode: bridge
    container_name: proxy
    restart: on-failure
    ports:
      - "80:80"
      - "443:443"
    environment:
      - ENABLE_IPV6=true
    volumes:
      - ./certs:/etc/nginx/certs
      - /var/run/docker.sock:/tmp/docker.sock:ro
  web1:
    image: php:7.2-apache
    network_mode: bridge
    container_name: web1
    restart: on-failure
    environment:
      - VIRTUAL_HOST=web1.com
  web2:
    image: php:7.2-apache
    network_mode: bridge
    container_name: web2
    restart: on-failure
    environment:
      - VIRTUAL_HOST=web2.com
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.21.2/docker-compose-`uname -s`-`uname -m` -o /usr/bin/docker-compose

cd /opt && docker-compose up -d

そして、いつものように terraform init, terraform plan, terraform apply を実行してください。Terraformから確認の連絡が来てから約4~5分後にパッケージのインストールが開始されます。

まとめ

マシン内のリソースを効率的に共有して複数のサービスを同時に実行する方法を学びました。あなたが覚えておくべき唯一のことは、サイトがより忙しくなる場合は、ECSインスタンスをアップグレードする必要がありますので、使用状況を追跡することです(余分なCPUを追加したり、RAMメモリを増加させることによって)。このソリューションは、多くのトラフィックを持つ小さなウェブサイトに最適であり、あなたは多くのホスティングの概念とその複雑さを学ぶことができます。あなたがまだ理解していない場合は、フォーラムでこの記事を参照してください。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ