00. 기본 설정
00-01. 디렉토리 구조
이번 실습에서 필요한 디렉토리 구조는 아래와 같습니다.
├── db
│   ├── Dockerfile
│   └── company.sql
├── docker-compose.yaml
└── app
    ├── Dockerfile
    ├── app.js
    ├── edit.html
    ├── insert.html
    ├── list.html
    └── package.json
00-02. EC2 생성
실습을 위한 EC2를 생성해주겠습니다.
OS는 Ubuntu 18.04를 이용하고 인터넷 통신이 가능한 서브넷에서 실습을 진행하겠습니다.

00-03. 패키지 설치
인스턴스를 생성했다면 SSH 접속 후 필요한 패키지를 설치합니다.

[docker 설치]
$ sudo su -
# apt-get update
# apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# apt-get update
# apt-get install docker-ce=5:20.10.14~3-0~ubuntu-bionic
[docker-compose 설치]
# apt install -y docker-compose
[npm 설치]
# apt install -y nodejs 
# apt install -y npm
[설치 확인]
# nodejs --version
# npm --version01. node js app 생성 및 Dockerfile 작성
01-00. 사전 준비
node js 애플리케이션을 위한 디렉토리를 생성합니다. [01. 단계]는 모두 아래에서 생성된 디렉토리 안에서 진행됩니다.# mkdir ~/app# cd ~/app
해당 디렉토리에서 packeage.json 파일을 만들기 위해 npm init 실행합니다. # npm init
실행 후 모두 Enter로 스킵하면 package.json파일이 만들어지는 것을 확인 할 수 있습니다.
파일을 자세히 확인해보고 싶다면 app 디렉토리에서 #cat package.json 명령을 이용하면 됩니다.
필요한 모듈을 설치합니다. # npm install express body-parser ejs cjs mysql http
01-01. package.json 수정
 필요한 모듈을 설치하게 되면 package.json 파일에 “dependencies”의 목록으로 패키지들이 추가 된 것을 확인 할 수 있습니다. 
만약 추가 되지 않았다면 직접 파일을 수정하여 아래 내용으로 변경하면 됩니다.
또한 다음 단계에서 생성한 node.js 애플리케이션을 실행하기 위해 필요한 몇가지 작업을 위해 package.json 파일 열어 내용을 수정합니다.
# vi package.json
{
  "name": "app",
  "version": "1.0.0",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.20.0",
    "ejs": "^3.1.6",
    "express": "^4.17.3",
    "fs": "0.0.1-security",
    "http": "0.0.1-security",
    "mysql": "^2.18.1"
  },
  "devDependencies": {},
  "description": ""
}
01-02. app.js 생성
app 디렉토리에서 app.js를 생성 후 아래 내용을 넣어줍니다.
# vi app.js
var fs = require('fs');
var ejs = require('ejs');
var http = require('http');
var mysql = require('mysql');
var express = require('express');
var bodyParser = require('body-parser');
var mySqlClient = mysql.createConnection({
    host: process.env.MYSQL_HOST,
    user: process.env.MYSQL_USER,
    password: process.env.MYSQL_PASSWORD,
    database: process.env.MYSQL_DATABASE
});
mySqlClient.connect(function (err) {
    if (err) throw err;
    console.log("Database Connected!");
});
var app = express();
http.createServer(app).listen(3000, function () {
    console.log('MYSQL_HOST: %s', process.env.MYSQL_HOST)
    console.log('Server running');
});
app.get('/', function (req, res) {
    fs.readFile('list.html', 'utf8', function (error, data) {
        if (error) {
            console.log('readFile Error');
        } else {
            mySqlClient.query('select * from employee', function (error, results) {
                if (error) {
                    console.log('error : ', error.message);
                } else {
                    res.send(ejs.render(data, {
                        prodList: results
                    }));
                }
            });
        }
    })
});
app.get('/delete/:id', function (req, res) {
    mySqlClient.query('delete from employee where id = ?', [req.params.id], function (error, result) {
        if (error) {
            console.log('delete Error');
        } else {
            console.log('delete id = %d', req.params.id);
            res.redirect('/');
        }
    });
});
app.get('/insert', function (req, res) {
    fs.readFile('insert.html', 'utf8', function (error, data) {
        if (error) {
            console.log('readFile Error');
        } else {
            res.send(data);
        }
    })
});
app.get('/edit/:id', function (req, res) {
    fs.readFile('edit.html', 'utf8', function (error, data) {
        mySqlClient.query('select * from employee where id = ?', [req.params.id], function (error, result) {
            if (error) {
                console.log('readFile Error');
            } else {
                res.send(ejs.render(data, {
                    product: result[0]
                }));
            }
        });
    });
});
app.use(bodyParser.urlencoded({
    extended: true
}));
app.post('/insert', function (req, res) {
    var body = req.body;
    mySqlClient.query('insert into employee(name, email, dept) values(?, ?, ?)', [body.name, body.email, body.dept], function (error, result) {
        if (error) {
            console.log('insert error : ', error.message);
        } else {
            res.redirect('/');
        }
    });
});
app.post('/edit/:id', function (req, res) {
    var body = req.body;
    mySqlClient.query('update employee set name=?, email=?, dept=? where id=?', [body.name, body.email, body.dept, body.id], function (error, result) {
        if (error) {
            console.log('update error : ', error.message);
        } else {
            res.redirect('/');
        }
    });
});01-03. edit.html 생성
app 디렉토리에서 node.js 애플리케이션의 편집을 위한 페이지를 생성하는 단계입니다.
 # wget <아래의 edit 다운로드 URL> 명령으로 파일을 다운 받거나 , vi 편집기로 파일을 생성합니다.
# vi edit.html
<!DOCTYPE html>
<html>
<head>
	<title>Edit Page</title>
</head>
<body>
	<h1>Edit Page</h1>
	<form method='post'>
		<fieldset>
			<legend>EDIT DATA</legend>
			<table>
				<tr>
					<td><lable>Id</lable></td>
					<td><input type="text" name="id" value="<%= employee.id  %>" disable /></td>
				</tr>
				<tr>
					<td><lable>Name</lable></td>
					<td><input type="text" name="name" value="<%= employee.name  %>" /></td>
				</tr>
				<tr>
					<td><lable>Email</lable></td>
					<td><input type="text" name="email" value="<%= employee.email %>" /></td>
				</tr>
				<tr>
					<td><lable>Dept</lable></td>
					<td><input type="text" name="dept"  value="<%= employee.dept %>" /></td>
				</tr>
			</table>
			<input type="submit" value="Update"/>
		</fieldset>
	</form>
</body>
</html>01-04. insert.html 생성
app 디렉토리에서 node.js 애플리케이션의 입력을 위한 페이지를 생성하는 단계입니다.
  # wget <아래의 insert 다운로드 URL> 명령으로 파일을 다운 받거나 , vi 편집기로 파일을 생성합니다. 
# vi insert.html 
<html>
<head>
    <title>List Page</title>
</head>
<body>
<h1>Insert Page</h1>
<form method="post">
    <fieldset>
        <legend>INSERT DATA</legend>
        <table>
            <tbody>
            <tr>
                <td>
                    <lable>Name</lable>
                </td>
                <td><input name="name" type="text"></td>
            </tr>
            <tr>
                <td>
                    <lable>Email</lable>
                </td>
                <td><input name="email" type="text"></td>
            </tr>
            <tr>
                <td>
                    <lable>Dept</lable>
                </td>
                <td><input name="dept" type="text"></td>
            </tr>
            </tbody>
        </table>
        <input type="submit" value="Insert">
    </fieldset>
</form>
</body>
</html>
01-05. list.html 생성
app 디렉토리에서 node.js 애플리케이션의 리스트를 보기 위한 페이지를 생성하는 단계입니다.
   # wget <아래의 list 다운로드 URL> 명령으로 파일을 다운 받거나 , vi 편집기로 파일을 생성합니다.  
# vi list.html  
<!DOCTYPE html>
<html>
<head>
    <title>List Page</title>
</head>
<body>
<h1>List Page</h1>
<a href='/insert'>Insert Data</a>
<hr/>
<table width="100%" border="1">
    <tr>
        <th>DELETE</th>
        <th>EDIT</th>
        <th>ID</th>
        <th>Name</th>
        <th>Email</th>
        <th>Dept</th>
    </tr>
    <% prodList.forEach(function(item, index){ %>
    <tr>
        <td><a href='/delete/<%= item.id %>'>DELETE</a></td>
        <td><a href='/edit/<%= item.id %>'>EDIT</a></td>
        <td><%= item.id %></td>
        <td><%= item.name %></td>
        <td><%= item.email %></td>
        <td><%= item.dept %></td>
    </tr>
    <% }); %>
</table>
</body>
01-06. Dockerfile 생성
위에서 만든 node.js, edit.html, insert.html, list.html을 이용하여 app 디렉토리에 Dockerfile를 만드는 단계입니다.
기본 이미지는 node:12를 사용하며 앞서 작업한 ‘app’ 디렉토리에 있는 파일들을 가져와 npm을 실행하게 되는 구조입니다.
이 애플리케이션은 3000번 포트로 노출할 것이므로 인스턴스의 보안 그룹의 3000번 포트를 열어주어야 합니다.
# vi Dockerfile
FROM node:12
WORKDIR /app
COPY package.json /app
COPY . /app
RUN npm install
EXPOSE 3000
CMD ["npm", "start"]01-07. “.dockerignore” 파일 생성
Dockerfile에서 app 디렉토리를 복사할 때 필요하지 않은 파일을 제거하는 단계입니다.
.dockerignore 파일을 생성하여 아래 내용을 넣어줍니다.
# vi .dockerignore
node_modules
npm-debug.log02. Mysql Dockerfile 작성
02-00. 사전 준비
MySQL을 위한 디렉토리를 생성합니다. [02. 단계]는 모두 아래에서 생성된 디렉토리 안에서 진행됩니다.
# mkdir ~/db# cd ~/db
02-01. Dockerfile 생성
mysql을 만들기 위해 db 디렉토리에 Dockerfile을 생성하는 단계입니다.
이 mysql은 3306 포트로 노출할 것이므로 인스턴스의 보안 그룹에 3306포트를 열어줍니다.
또한 이미지가 생성 될 때 table이 생성될 수 있도록 만든 sql 파일이 실행될 수 있도록 합니다.
# vi Dockerfile
FROM mysql:5.7
EXPOSE 3306
COPY ./company.sql /docker-entrypoint-initdb.d/
CMD ["mysqld"]
02-02. company.sql
db 디렉토리에 mysql 동작 시 실행 되어야 할 sql 코드를 담은 sql 파일을 생성합니다.
# vi company.sql
USE company;
CREATE TABLE employee
(
    id    INT          NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name  VARCHAR(50)  NOT NULL,
    email VARCHAR(100) NOT NULL,
    dept  VARCHAR(50)  NOT NULL
);
03. docker-compose
03-01. docker-compose.yml
‘app’, ‘db’ 디렉토리 밖에서 docker-compose.yml 파일을 생성합니다.
각각의 디렉토리에서 생성한 Dockerfile을 이용하여 build를 진행하고 컨테이너를 실행하게 됩니다.
vi ~/docker-compose.yml
version: "3.4"
services:
  db:
    container_name: "mysql_db"
    cap_add:
      - SYS_NICE
    build:
      context: ./db
    volumes:
      - ./data:/var/lib/mysql
    environment:
      MYSQL_DATABASE: company
      MYSQL_ROOT_PASSWORD: 1234
      MYSQL_USER: user
      MYSQL_PASSWORD: 1234
      TZ: Asia/Seoul
    ports:
      - "3306:3306"
    healthcheck:
      test: [ "CMD", "mysql", "-uuser", "-p1234", "--execute", "show databases;" ]
      interval: 30s
      timeout: 30s
      retries: 3
      start_period: 30s
  app:
    container_name: "node_app"
    build:
      context: ./app
    environment:
      MYSQL_HOST: db
      MYSQL_USER: user
      MYSQL_PASSWORD: 1234
      MYSQL_DATABASE: company
    ports:
      - "3000:3000"
    links:
      - db
    depends_on:
      - db
    command: [ "npm", "start" ]
03-02. docker-compose up
위에서 생성한 docker-compose.yml 파일을 실행해줍니다.
이때 명령을 실행하는 경로는 docker-compose.yml 파일이 있는 디렉토리 안입니다.
# docker-compose up

만약 오류가 날 경우 docker-compose down 후 docker-compose up을 다시 실행해줍니다.
03-03. 실행 확인
브라우저에 접속하여 ‘<인스턴스IP>:3000’에 접속하여 결과를 확인합니다.

- INSERT로 한글을 넣고 싶을 경우, docker로 위의 구성을 하게 되면 한글을 지원하지 않기 때문에 OS와 MySQL의 문자셋 재설정을 추가로 해주어야 합니다.
