[Hands On] ECR을 이용한 mysql/node.js on Docker 1 Session

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 --version


01. 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.log


02. 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의 문자셋 재설정을 추가로 해주어야 합니다.