👋

Fargate(Nginx)->EC2を構築してみた

2024/01/19に公開

はじめに

EC2(Nginx)->EC2というプロキシ構成は以下記事で行ったことはありましたが、Fargate(Nginx)->EC2という構成でしてみたかったのでやってみました。

https://zenn.dev/kazu_o/articles/06e294ebe605be

セキュリティグループ作成

事前にFargateとEC2用のセキュリティグループを作成しておきます。

  • Fargate
    • インバウンド:HTTP・自宅のIP許可
    • アウトバウンド:フルオープン
  • EC2
    • インバウンド:HTTP・Fargateのセキュリティグループ許可
    • アウトバウンド:フルオープン

EC2構築

以下の設定で作成しました。

  • ami-0506f0f56e3a057a4(Amazon Linux2023選択したらデフォルトで選択されるAMI)
    • al2023-ami-2023.3.20240108.0-kernel-6.1-x86_64
  • パブリックIPアドレスあり
    • 本来ならEC2はプライベートサブネットに置くと思いますが、NginxインストールにNATゲートウェイの料金がかかるのでパブリックサブネットに置きます
  • 事前作成したEC2用のセキュリティグループ
  • SessionManagerで接続できるIAMロールアタッチ
  • t2.micro

EC2にNginxインストール・設定

まずNginxのインストールから起動確認まで行います。

$ sudo dnf -y update
$ sudo dnf -y install nginx
$ sudo systemctl start nginx
$ systemctl status nginx
● nginx.service - The nginx HTTP and reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; disabled; preset: disabled)
     Active: active (running) since Thu 2024-01-18 12:38:07 UTC; 19s ago
    Process: 18475 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)
    Process: 18486 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
    Process: 18489 ExecStart=/usr/sbin/nginx (code=exited, status=0/SUCCESS)
   Main PID: 18518 (nginx)
      Tasks: 2 (limit: 1114)
     Memory: 2.2M
        CPU: 57ms
     CGroup: /system.slice/nginx.service
             ├─18518 "nginx: master process /usr/sbin/nginx"
             └─18519 "nginx: worker process"

起動確認ができたので、ドキュメントルートの場所を確認の上、ドキュメントを単純な内容に変更します。
なお、catの出力内容は関係箇所のみ抜粋しています。

$ cat /etc/nginx/nginx.conf
    server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        root         /usr/share/nginx/html;
$ sudo vi /usr/share/nginx/html/index.html
Amazon Linux 2023 Nginx!!

これでEC2にアクセスすると、「Amazon Linux 2023 Nginx!!」が返却されます。

Fargate用のDockerイメージ作成

プロキシに利用するのもEC2と同じくNginxです。
まずは適当なディレクトリを作成し、同ディレクトリに移動した上でDockerfileを作成します。

Dockerfile
FROM nginx
COPY ./default.conf /etc/nginx/conf.d/

Nginx用の設定ファイルdefault.confをDockerfileと同じ階層に置きます。
なお、以下の設定ファイルは事前にベースイメージのNginxコンテナにdocker execして確認した内容を流用しています。
proxy_passのコメントアウトを除外した上でEC2のプライベートアドレスを設定し、もう1箇所のlocation /をコメントアウトしています。
もう1箇所のlocationをコメントアウトしているのは、重複してエラーが発生するからです。

default.conf
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

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

    #location / {
    #    root   /usr/share/nginx/html;
    #    index  index.html index.htm;
    #}

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy
    location / {
        proxy_pass http://10.0.0.120;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

階層構造は以下になります。

$ tree
.
├── Dockerfile
└── default.conf

次に、イメージを作成し、正常に作成できたかチェックしていきます。

$ docker build -t my-nginx . 
$ docker container run -d --name my-nginx my-nginx
$ docker exec -it my-nginx bash   
# curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>An error occurred.</h1>
<p>Sorry, the page you are looking for is currently unavailable.<br/>
Please try again later.</p>
<p>If you are the system administrator of this resource then you should check
the error log for details.</p>
<p><em>Faithfully yours, nginx.</em></p>
</body>
</html>
# exit

上記ではイメージ作成後、コンテナを立ち上げコンテナに入っています。
そして、コンテナ内でcurl localhostを実行してエラーのドキュメントが返ってきています。
この挙動は、Nginxコンテナがプロキシ先のIPアドレス10.0.0.120に到達できないので想定通りの挙動になります。
念のため、10.0.0.120に接続しようとしてエラーになっているかNginxコンテナのログを確認します。

$ docker logs my-nginx 
2024/01/18 13:10:39 [error] 22#22: *1 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", upstream: "http://10.0.0.120:80/", host: "localhost"
$ docker container stop my-nginx
$ docker rm my-nginx

想定通りログからも10.0.0.120への接続に失敗している状況がわかったので、コンテナを停止・削除します。

ECRリポジトリにプッシュ

想定通りのDockerイメージが作成できたので、今度はFargateで利用するためにECRにプッシュします。

まずはECRのプライベートリポジトリを作成します。
今回リポジトリの名前は my-nginxとしました。

リポジトリを作成するとプッシュコマンドが確認できるので、そのコマンドを参考にDockerイメージをプッシュしていきます。
必要な人はAWS CLIコマンドに --profile を設定しましょう。
xxx.dkr.ecr....のxxxはAWSアカウントIDです。

$ aws ecr get-login-password --profile xxx | docker login --username AWS --password-stdin xxx.dkr.ecr.ap-northeast-1.amazonaws.com 
Login Succeeded

Logging in with your password grants your terminal complete access to your account. 
For better security, log in with a limited-privilege personal access token. Learn more at https://docs.docker.com/go/access-tokens/

$ docker tag my-nginx:latest xxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-nginx:latest

$ docker image ls | grep my-nginx
xxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-nginx      latest            8e5447186467   21 minutes ago   187MB
my-nginx                                                        latest            8e5447186467   21 minutes ago   187MB

$ docker push xxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-nginx:latest
The push refers to repository [xxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-nginx]
00252c7b058e: Pushed 
b074db3b55e1: Pushed 
e50c68532c4a: Pushed 
f6ba584ca3ec: Pushed 
01aaa195cdad: Pushed 
2a13e6a7cca6: Pushed 
370869eba6e9: Pushed 
7292cf786aa8: Pushed 
latest: digest: sha256:fc8fd14e55fed8bf64f9f479aa502bad51980467870e53f48b87db7c6e09ae56 size: 1985

以上のコマンドによりECRへイメージのプッシュが完了しました。
本当にプッシュが完了したかはAWSマネジメントコンソールからECRリポジトリを確認しましょう。

Fargateタスク作成

まずはECSクラスターを作成します。

次に、タスク定義を作成します。
作成はコンソール上でぽちぽち行いましたが、JSONにすると以下の内容です。
主な設定は次のとおりです。

項目
起動タイプ Fargate
コンテナのイメージURI xxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-nginx:latest(ECRリポジトリのイメージ)
コンテナポート 80
{
    "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:xxx:task-definition/Proxy-Nginx:1",
    "containerDefinitions": [
        {
            "name": "Nginx",
            "image": "xxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-nginx:latest",
            "cpu": 0,
            "portMappings": [
                {
                    "name": "nginx-80-tcp",
                    "containerPort": 80,
                    "hostPort": 80,
                    "protocol": "tcp",
                    "appProtocol": "http"
                }
            ],
            "essential": true,
            "environment": [],
            "environmentFiles": [],
            "mountPoints": [],
            "volumesFrom": [],
            "ulimits": [],
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-create-group": "true",
                    "awslogs-group": "/ecs/Proxy-Nginx",
                    "awslogs-region": "ap-northeast-1",
                    "awslogs-stream-prefix": "ecs"
                },
                "secretOptions": []
            }
        }
    ],
    "family": "Proxy-Nginx",
    "executionRoleArn": "arn:aws:iam::xxx:role/ecsTaskExecutionRole",
    "networkMode": "awsvpc",
    "revision": 1,
    "volumes": [],
    "status": "ACTIVE",
    "requiresAttributes": [
        {
            "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
        },
        {
            "name": "ecs.capability.execution-role-awslogs"
        },
        {
            "name": "com.amazonaws.ecs.capability.ecr-auth"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
        },
        {
            "name": "ecs.capability.execution-role-ecr-pull"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
        },
        {
            "name": "ecs.capability.task-eni"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.29"
        }
    ],
    "placementConstraints": [],
    "compatibilities": [
        "EC2",
        "FARGATE"
    ],
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "cpu": "1024",
    "memory": "3072",
    "runtimePlatform": {
        "cpuArchitecture": "X86_64",
        "operatingSystemFamily": "LINUX"
    },
    "registeredAt": "2024-01-18T13:27:42.645Z",
    "registeredBy": "arn:aws:iam::xxx:user/xxx",
    "tags": []
}

最後に、ECSサービスを作成してタスクを起動します。
主な設定は次のとおりです。

項目
起動タイプ Fargate
タスク定義 上記で作成したタスク定義
必要なタスク 1
ネットワーキングのサブネット EC2と同じVPCのパブリックサブネット
ネットワーキングのセキュリティグループ 事前作成しておいたFargateのセキュリティグループ
ネットワーキングのパブリックIP オン

ネットワーキングの設定さえ間違えなければ問題ありません。
ECSサービスを作成すると、少し時間をおいてタスクが起動されます。

疎通確認

タスクが起動されたら、パブリックIPアドレスをメモしましょう。
そして、ローカルPCからタスクのパブリックIPアドレス宛にcurlを実行します。
ブラウザからのアクセスでも問題ありません。

$ curl 18.183.73.134
Amazon Linux 2023 Nginx!!

EC2に設定した内容が返却されたので、Fargate(Nginx)->EC2でプロキシできたことが確認できました。
続いて、EC2インスタンスのアクセスログ/var/log/nginx/access.logも確認してみましょう。

$ cat /var/log/nginx/access.log
10.0.0.174 - - [18/Jan/2024:13:41:27 +0000] "GET / HTTP/1.0" 200 26 "-" "curl/8.1.2" "-"

実際はブラウザからもアクセスしているので上記より多くのアクセスログがありましたが、可視性を考慮し省略しています(以降同様)。
10.0.0.174からアクセスされていますが、これはタスクのプライベートIPアドレスです。
自宅のIPアドレスではないので、絶対に自分が接続したという実感が湧きません。
実際のクライアントのIPアドレスを知りたい場合は事前にプロキシのFargateのNginxの設定を変更する必要があります。

https://qiita.com/yakumo3/items/dc38a9b6c19194a88922

上記記事を参考にしてイメージを作り直せばできると思いますが、今回はUserAgentを変更して自分がアクセスしたという実感を得ようと思います。
ローカルPCでcurlを実行します。

$ curl 18.183.73.134 -A "OreOre"
Amazon Linux 2023 Nginx!!

上記でUserAgentを指定したので、アクセスログにはOreOreが残っているはずです。
EC2のアクセスログを確認します。

$ cat /var/log/nginx/access.log
10.0.0.174 - - [18/Jan/2024:13:46:47 +0000] "GET / HTTP/1.0" 200 26 "-" "OreOre" "-"

UserAgentがOreOreになっていました。
これで自分がアクセスしたログであることが一目瞭然ですね。

おわりに

この記事が誰かのお役に立てれば幸いです。

Discussion