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

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

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

はじめに

今回の記事の目的

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

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

環境

  • ローカル環境はvagrantLinux、ubuntu64_18)
  • dockerはインストール済。

tomiko0404.hatenablog.com

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

以前*1、dockerでReactアプリを作成した際は、

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

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

そこで、Dockerfile上に

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

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

フォルダ構成

プロジェクトフォルダのフォルダ構成は以下。 動作確認用の簡易アプリで、create-react-appで作成したもの。

  • App.jsxなど他のファイルがあってもOK。
  • ローカルで実行して動作を確認すると思うので、基本的にはapp/配下にnode_modulesフォルダが必要になる。node_modulesはappフォルダ内でnpm installを実行すれば自動的に作成される。node_modulesを作成する場合は、後の手順(【補足】dockerコンテナにコピーしたくないファイルがある場合)を実施したほうが良い。
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が見つからないのでエラーとなる。

npmの各種パッケージを、package.jsonに従ってインストールする。

RUN npm install

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

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

ENV PORT 80

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

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

CMD ["npm", "start" ] 

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

【補足】dockerコンテナにコピーしたくないファイルがある場合

appフォルダ内にコピーしたくないファイルがある場合は、.dockerignoreファイルを作成する必要がある。

node_modulesフォルダ等、ローカルでアプリを動かしてみる際に利用しているフォルダがあるのであれば、コピーしない設定を記載しておいたほうがよさそう。

tomiko0404.hatenablog.com

dockerイメージのビルド・ECRプッシュ

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

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

毎回やることだけ簡単にメモ

イメージをビルドする。

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

# "react-practice-6" の箇所は、イメージ名を指定する

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

ただし、イメージにタグをつける手順については、AWSコンソールのコピペではなく

$ docker tag 第1引数 第2引数

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

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

また、レジストリに対してローカル環境を認証する手順では、以下のようなエラーがでることがある。

Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/auth": dial unix /var/run/docker.sock: connect: permission denied

その場合は以下コマンドでファイルアクセス権限をつけてあげる。

sudo chmod ///var/run/docker.sock 666

ECS立ち上げ

ECSのタスク立ち上げは前回の記事通りで問題ない。

タスクを立ち上げた後、ソースコードを変更した場合は、

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

が必要となる。

参考記事

ALBを利用する場合

ALBを利用してコンテナを立ち上げる場合は、前回の記事ではなくこちら。 tomiko0404.hatenablog.com

ブルーグリーンデプロイメントを行ないたい場合

CodePipelineを利用して、ブルーグリーンデプロイメントを実施できるようにするにはこちら。 tomiko0404.hatenablog.com

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

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

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 ブログ

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

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

Reactアプリ(または React×TypeScriptアプリ)を作る方法メモ - エンジニアを目指す日常ブログ

  • 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"
    ]
  }
}