1. 설계
가. 기능 명세
•
Client 입력값을 Server가 받아서 DB에 저장
•
DB 값을 Server를 통해 Client로 출력
2. Server
가. 기본 설정
•
package.json 작성(npm init)
•
필요한 스크립트, 종속성 추가 및 설치(dependacies, npm install)
"scripts": {
"start": "node index.js",
"nodemon": "nodemon index.js",
"frontend": "npm run start --prefix client",
"dev": "concurrently \"npm run nodemon\" \"npm run start --prefix client\""
},
"dependencies": {
"body-parser": "^1.18.3",
"cors": "^2.8.5",
"debug": "^4.1.1",
"express": "^4.17.1",
"mongoose": "^5.10.0",
"saslprep": "^1.0.3",
"supports-color": "^7.1.0"
},
"devDependencies": {
"concurrently": "^4.1.0",
"nodemon": "^1.19.1"
}
JavaScript
복사
나. 템플릿 작성
•
index.js 작성
// 필요한 모듈 사용
const express = require("express");
const bodyParser = require("body-parser");
// express server 생성
const app = express();
// json 형태 요청에 대해 파싱 가능
app.use(bodyParser.json());
app.get("/", function (req, res) {
console.log("hello");
});
// 5000포트 사용하여 app 시작
app.listen(5000, () => {
console.log("app start at 5000 port");
});
JavaScript
복사
다. DB 구성
•
DB 연결(mongoDB, mongoose)
const mongoose = require("mongoose");
const connect = mongoose
.connect(
"URL from Mongo DB",
{
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false,
}
)
.then(() => console.log("MongoDB Connected..."))
.catch((err) => console.log(err));
JavaScript
복사
•
DB Schema 및 model 설정
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const TodosSchema = mongoose.Schema(
{
todoId: {
type: Schema.Types.ObjectId,
},
todoTitle: {
type: String,
},
},
{ timestamps: true }
);
const Todos = mongoose.model("Todos", TodosSchema);
module.exports = { Todos };
JavaScript
복사
라. API 작성
•
DB 내 todos 출력 API
app.get("/api/printTodos", function (req, res) {
Todos.find({}, function (err, result) {
if (err) return res.status(400).send(err);
res.status(200).send(result);
});
});
JavaScript
복사
•
Client 입력값 DB 저장 API
app.post("/api/inputTodos", function (req, res) {
const todo = new Todos(req.body);
todo.save((err, result) => {
if (err) return res.json({ success: false, err });
return res.status(200).json({ success: true });
});
});
JavaScript
복사
•
DB 내 모든 todos 삭제
// todos 모두 삭제
app.get("/api/removeTodos", function (req, res) {
Todos.remove({}, function (err, result) {
if (err) return res.status(400).send(err);
res.status(200).send(result);
});
});
JavaScript
복사
3. Client
가. 기본 설정
•
React 설치(npx create-react-app .)
→ 해당 디렉토리에 react-app 설치
•
종속성 추가
→ axios
나. 템플릿 작성
•
UI 생성(JSX 부분 작성)
function App() {
return (
<div className="App">
<header className="App-header">
<div className="container">
<form className="inputBox">
<input type="text" placeholder="입력란" />
<button type="submit">입력</button>
</form>
</div>
</header>
</div>
);
}
JavaScript
복사
다. API 처리
•
Todo-list 출력
const [todos, setTodos] = useState([]);
useEffect(() => {
axios.get("/api/printTodos").then((response) => {
setTodos(response.data);
});
}, []);
// JSX 부분
<div className="container">
{todos &&
todos.map((todo, index) => <li key={index}>{todo.todoTitle}</li>)}
<form className="inputBox">
<input type="text" placeholder="입력란" />
<button type="submit">입력</button>
</form>
</div>
JavaScript
복사
•
todo 입력
const [inputValue, setinputValue] = useState("");
const changeHandler = (event) => {
setinputValue(event.currentTarget.value);
};
const submitHandler = (event) => {
event.preventDefault();
axios
.post("/api/inputTodos", { todoTitle: inputValue })
.then((response) => {
if (response.data.success) {
setTodos([...todos, response.data]);
setinputValue("");
} else {
alert("falied to update todos");
}
});
};
// JSX 부분
<form className="inputBox" onSubmit={submitHandler}>
<input
type="text"
placeholder="입력란"
value={inputValue}
onChange={changeHandler}
/>
<button type="submit">입력</button>
</form>
JavaScript
복사
•
todo-list 삭제
const removeHandler = (event) => {
event.preventDefault();
axios.get("/api/removeTodos").then((response) => {
setTodos([]);
setinputValue("");
});
};
// JSX 부분
<form className="removeButton" onSubmit={removeHandler}>
<button type="submit">모두 삭제</button>
</form>
JavaScript
복사
라. Proxy 설정
•
npm install http-proxy-middleware —save
•
다음의 경로에 아래와 같이 파일 생성 client/src/setupProxy.js
// setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
'/api',
createProxyMiddleware({
// 서버 포트
target: 'http://localhost:5000',
changeOrigin: true,
})
);
};
JavaScript
복사
4. 개발 환경
가. Dockerfile 작성
•
FROM node:alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY ./ ./
CMD [ "npm", "run", "start" ]
Docker
복사
•
FROM node:alpine
WORKDIR /app
COPY ./package.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
Docker
복사
•
Ngnix Dockefile 작성
→ proxy 기능을 위해 Ngnix가 필요하며 default.conf에 세부사항 명시
나. docker-compose 작성
version: "3"
services:
client:
build:
dockerfile: Dockerfile.dev
context: ./client
volumes:
- /app/node_modules
- ./client:/app
stdin_open: true
nginx:
restart: always
build:
dockerfile: Dockerfile
context: ./nginx
ports:
- "3000:80"
server:
build:
dockerfile: Dockerfile.dev
context: ./server
container_name: app_server
volumes:
- /app/node_modules
- ./server:/app
YAML
복사
•
개발환경 테스트 실행(docker-compose up)
→ 빌드 시도했을 경우, docker-compose up —build
→ 이미 빌드된 파일이 있을 경우, docker-compose up 입력 시 기존 빌드파일 사용함. 따라서 변경사항을 반영하기 위해 —build 키워드 입력할 것
5. 운영 환경 배포
가. Dockerfile 작성
•
client Dockerfile
→ nginx 필요
# client/nginx/default.conf
server {
listen 3000;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
YAML
복사
# client/Dockerfile
FROM node:alpine as builder
WORKDIR /app
COPY ./package.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx
EXPOSE 3000
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY /app/build /usr/share/nginx/html
Docker
복사
•
server Dockerfile
FROM node:alpine
WORKDIR /app
COPY ./package.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "start"]
YAML
복사
•
github에 배포하려는 프로젝트 repository 생성 후 commit
나. Travis CI Dokcer Hub AWS EB Steps
1.
깃헙 push
2.
Travis CI가 깃헙 코드 자동으로 가져옴
3.
Travis CI가 해당 코드로 테스트 코드 실행
4.
테스트 성공 시 운영환경 이미지 빌드
5.
빌드된 이미지를 Docker Hub로 전송
→ Travis CI가 Github으로부터 source를 가져올 수 있도록 activate 설정
→ 빌드 작업할 수 있도록 .travis.yml 파일 작성
language: generic
sudo: required
# 앱을 도커 환경에서 실행할 것 명시
services:
- docker
# 테스트 이미지 생성
before_install:
- docker build -t gentlemj/todolist-app -f ./client/Dockerfile.dev ./client
# 테스트 이미지에 따라 테스트 수행
script:
- docker run -e CI=true gentlemj/todolist-app npm run test
after_success:
# 테스트 성공 시, 프로젝트의 운영버전 이미지 빌드 설정
- docker build -t gentlemj/docker-client ./client
- docker build -t gentlemj/docker-server ./server
- docker build -t gentlemj/docker-nginx ./nginx
# 도커허브 로그인
- echo "$DOCKER_HUB_PASSWORD" | docker login -u "$DOCKER_HUB_ID" --password-stdin
# 빌드된 이미지를 도커허브에 전송
- docker push gentlemj/docker-client
- docker push gentlemj/docker-server
- docker push gentlemj/docker-nginx
YAML
복사
6.
Docker Hub가 AWS EB에 이미지 전송
→ EB 새 환경 설정
•
AWS EB 서비스에서 새 EB 환경의 이름 및 플랫폼 지정(Docker, Multi-container)
→ .travis.yml 파일 배포부분 작성
deploy:
# 외부 서비스 표시
provider: elasticbeanstalk
# EB 접속하여 직접 확인
region: "ap-northeast-2"
app: "todolist-app"
env: "TodolistApp-env"
# Traivs CI에서 EB로 이미지 전송 시 S3를 거쳐가므로
# S3의 bucket을 작성
bucket_name: elasticbeanstalk-ap-northeast-2-297348835951
bucket_path: "todolist-app"
# master branch에 psuh할 때만 배포 실행
on:
branch: master
access_key_id: $AWS_ACCESS_KEY
secret_access_key: $AWS_SECRET_ACCESS_KEY
YAML
복사
→ Travis CI의 AWS 접근을 위한 API key 생성
1.
AWS dashboard에서 IAM 선택 후 IAM 사용자 추가
2.
사용자 이름 및 AWS 엑세스 유형 선택(프로그래밍 방식)
3.
기존 정책 직접 연결 카테로리 내 AWSElasticbeanstalkFullAccess 권한만 허용
4.
사용자 생성 완료 후, 엑세스 키 ID와 비밀 엑세스 키를 Travis CI 사이트의 설정 부분에 추가
5.
.travis.yml 파일에 위의 키를 활용한 Access 부분 축
access_key_id: $AWS_ACCESS_KEY
secret_access_key: $AWS_SECRET_ACCESS_KEY
YAML
복사
7.
AWS EB가 배포 실행
→ Dockerrun.aws.json 작성
EB의 프로세스 처리 명세
{
"AWSEBDockerrunVersion": 2,
"containerDefinitions": [
{
"name": "client",
// Docker hub에 올린 Docker image 이름 작성
"image": "gentlemj/docker-client",
// 다른 컨테이너에서 hostname 활용해서 접근 가능
"hostname": "client",
// 컨테이너 구성 실패 시 나머지 컨테이너에 영향을 주지 않고 종료
// 반면 true일 경우, 다른 컨테이너도 종료
"essential": false,
// 컨테이너에 따라 메모리 할당
"memory": 128
},
{
"name": "server",
"image": "gentlemj/docker-server",
"hostname": "server",
"essential": false,
"memory": 128
},
{
"name": "nginx",
"image": "gentlemj/docker-nginx",
"hostname": "nginx",
"essential": true,
// 컨테이너의 네트워크 지점을 호스트의 지점과 매핑
"portMappings": [
{
"hostPort": 80,
"containerPort": 80
}
],
"links": ["client", "server"],
"memory": 128
}
]
}
JSON
복사
→ 위의 모든 설정 완료 후 '.travis.yml' push하면 배포 완료
질문) proxy 설정 관련