エンジニアを目指す日常ブログ

日々勉強したことのメモ。間違っていたらコメント等で教えてください。

ECS(Fargate)でReactアプリを起動する方法メモ

はじめに

今回の記事の目的

nginxを利用した簡単なアプリをコンテナ上で起動することができたので、今回はECSのコンテナにReactアプリを載せることにする。

nginxを利用した簡単なアプリをコンテナ上で起動した方法
tomiko0404.hatenablog.com

環境

ローカル環境でdockerイメージを作成する

以前(vagrant上にdockerコンテナを立ち上げる方法メモ - エンジニアを目指す日常ブログ)、dockerでReactアプリを作成した際は、

  • docker-composeを利用
  • vagrant上のワークスペースとdocker上のフォルダを同期する設定volumesを入れている
  • dockerコンテナ上で、React(Nodeプロジェクト)のインストールや実行(npm start)を実施

としていたが、今回はECS(Fargate)で構築するため、dockerコンテナに入れないし、ローカル環境とフォルダ同期もできない。

そこで、Dockerfile上に

  • ソースコードをdocker上にコピー
  • 必要なNodeモジュールのインストール
  • プロジェクトの実行

を記載して指示する必要がある。

フォルダ構成

プロジェクトフォルダのフォルダ構成は以下。

React-practice-6
├── Dockerfile
└── app
    ├── package.json
    ├── public
    │   └── index.html
    └── src
        └── index.js

Dockerfileの中身

Dockerfileの中身は以下。

FROM node:14.15.4-slim

RUN apt-get update && apt-get install -y locales \
    && locale-gen en_US.UTF-8 \
    && localedef -i en_US -f UTF-8 en_US.UTF-8 \
    && echo "export LC_ALL=en_US.UTF-8" >> ~/.bashrc \
    && ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

COPY ./app /app

WORKDIR /app

RUN npm install

ENV PORT 80

CMD ["npm", "start" ] 

RUN apt-get install -y \
    wget \
    curl \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

ポイントとして、COPYコマンドやRUNコマンドは、dockerイメージをビルドする際に一度だけ実行される。一方で、CMDコマンドは、コンテナを起動するたびに実行される。 そのため

#  NG例
RUN npm start

と記載するとビルドが上手くいかなかった。

以下コマンドで、vagrant./appフォルダの中身をコンテナ上の/appフォルダにコピーする。

COPY ./app /app

ここで、1つ目の引数(ローカルのフォルダ)には、

  • 絶対パスは利用不可
  • カレントフォルダより上のフォルダは設定負荷

という制約がある。

次に以下コマンドで、フォルダ移動する。

WORKDIR /app

これを忘れるとコンテナ上でpackage.jsonが見つからないのでエラーとなる。

Nodeをインストールする。

RUN npm install

これはイメージのビルド時に実行で問題ない。

次に環境変数の設定を実施。

ENV PORT 80

Nodeプロジェクトを実行したときにポート80でアクセスできるようになる。

最後にプロジェクトの実行コマンド。

CMD ["npm", "start" ] 

これは起動コマンドなので、コンテナ起動時に実行してほしいものである。

【補足】appフォルダ内にコピーしたくないファイルがある場合

.dockerignoreファイルを作成する必要がある。 tomiko0404.hatenablog.com

dockerイメージのビルド・ECRプッシュ・タスク起動

あとは前回の記事と同じように実施していけばOK。

nginxを利用した簡単なアプリをコンテナ上で起動した方法
tomiko0404.hatenablog.com

イメージをビルドする。

$ sudo docker build -t react-practice-6 .
$ sudo docker images

ECRにリポジトリを作成し、「pushコマンドの表示」に従って実施する。

ただし、イメージにタグをつける手順は

$ docker tag 第1引数 第2引数

のコマンドのうち第1引数部分を自分で決めたイメージ名に変更する必要がある。

// 例
$ docker tag react-practice-6 xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/ecr-react-practice-6:latest

ECSのタスク立ち上げは前回の記事通りで問題ないが、ソースコードを変更した場合は、

  • dockerイメージの再ビルド
  • dockerイメージへのタグ付け
  • ECRへの再プッシュ
  • タスクの立ち上げ直し

が必要となる。

追記:後日イメージビルド時にエラーが出た

後日同じことをやろうとしたらビルドでエラーが発生。

E: Failed to fetch http://security.debian.org/debian-security/pool/updates/main/o/openssl1.0/libssl1.0.2_1.0.2u-1~deb9u4_amd64.deb  404  Not Found [IP: 151.101.66.132 80]
E: Failed to fetch http://security.debian.org/debian-security/pool/updates/main/o/openssl/openssl_1.1.0l-1~deb9u3_amd64.deb  404  Not Found [IP: 151.101.66.132 80]
E: Unable to fetch some archives, maybe run apt-get update or try with --fix-missing?
The command '/bin/sh -c apt-get install -y     wget     curl     && apt-get clean     && rm -rf /var/lib/apt/lists/*' returned a non-zero code: 100

Dockerfileの最後に記載している以下部分でエラーが発生している模様。

RUN apt-get install -y \
    wget \
    curl \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

色々試した結果、既にあったnodeイメージを削除してやり直すとうまくいった。FROM node:14.15.4-slimでベースとなっている部分が何かしらおかしくなっていた?

REPOSITORY                                                                TAG            IMAGE ID       CREATED         SIZE
node                                                                      14.15.4-slim   2f75d89d8162   7 months ago    167MB

dockerイメージを削除するコマンド(メモ)

$ docker rmi [イメージID]

これで消えない場合は、コンテナで使われている場合があるので

$ docker ps -a
$ docker rm [コンテナID]

でコンテナを削除する。それでも消えなければ

$  docker rmi -f [イメージID]

おわりに

ECS Fargate上にReactアプリを立ち上げることができた。 基本を習得するためにDockerfileのみで実施したが、編集するたびにビルドをやり直すのは大変そう。

AWSでもdocker-composeを利用できる機能がリリースされているようなので、使ったほうがよさそう。

Docker Compose と Amazon ECS を利用したソフトウェアデリバリの自動化 | Amazon Web Services ブログ

参考:その他のファイルの中身

あくまで動作確認用の簡易アプリ。index.js以外は、テンプレートそのままである。 ローカルでnpx create-react-appすれば最低限のファイルは作ってくれる。

dockerコンテナ(nodeイメージ)上でReactアプリを作る - エンジニアを目指す日常ブログ

  • index.js
import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
  <React.StrictMode>
    <h1>React-Practice-6</h1>
    <h2>app edit</h2>
  </React.StrictMode>,
  document.getElementById('root')
);
  • index.html  ※
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>
  • package.json ※前に作ったアプリからコピーしてきたもの。必要な記載はアプリによって変わるのであくまで参考。
{
  "name": "jakee-atomic-design",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-dom": "^5.3.0",
    "react-scripts": "4.0.3",
    "recoil": "^0.4.1",
    "styled-components": "^5.3.1",
    "web-vitals": "^1.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}