React,MongoDB,Express,Nginx 도커 개발 환경 구성하기

이번 시간에는 대중적으로 많이 쓰이는 환경을 도커로 하나씩 구축해볼 예정이다.

구성 스택은 다음과 같다.

  1. Client : React
  2. Api Server : Node Express
  3. DB : Mongo
  4. Server : Nginx

Root

우선 환경을 구성한다. 기본적으로 3개의 폴더와 docker-compose.yaml 파일로 구성된다.

- client
	- ...sources
	- Dockerfile.dev
- nginx
	- default.conf
	- Dockerfile.dev	
- server
	- ...sources
	- Dockerfile.dev
- docker-compose.yaml	

Client

create-react-app 을 이용해서 구성한다.

- ./client

# create-react-app client

dockerfile.dev 파일을 만들어 준다.

# 노드가 담긴 alpine 이미지 가져오기
FROM node:10.15-alpine

//작업할 디렉토리 설정
WORKDIR "/app"

//npm install을 캐싱하기 위해 package.json만 따로 카피
COPY ./package.json ./
RUN npm install
 
// 소스 복사 
COPY . .

//client 소스 실행
CMD ["npm","run","start"]

Server

express generator를 이용해서 만들 예정이다.

npm install -g express-generator

express server

package.json에는 실시간 개발환경을 위해서 nodemon을 추가해주자.

  "scripts": {
    "dev": "nodemon ./bin/www",
    "start": "node ./bin/www"
  },

서버쪽에도 dockerfile.dev을 만들어보자

FROM node:10.15-alpine

WORKDIR "/app"

COPY ./package.json ./

RUN npm install -g nodemon \
 && npm install
 
COPY . .

CMD ["npm","run","dev"]

Nginx

프록시 서버로 동작될 예정이다.

client와 server 부분을 따로 프록시로 관리한다.

server는 / 로 들어오는 요청을 /api 로 바꿔준다.

파일 두개를 생성해주자

  • default.conf
# nginx/default.conf

upstream client {
    server client:3000;
}

upstream server {
    server server:3050;
}

server {
    listen 80;

    location / {        
        proxy_pass http://client;
    }

    location /sockjs-node {
        proxy_pass http://client;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }

    location /api {
        rewrite /api/(.*) /$1 break;
        proxy_pass http://server;
    }
}
  • Dockerfile.dev
# nginx/Dockerfile.dev

FROM nginx
COPY ./default.conf /etc/nginx/conf.d/default.conf

Docker-compose

이제 기본 생성된 파일들을 묶어주고 간편하게 한번에 올릴 수 있게 하자.

  • docker-compose.yaml
version: "3"
services:
  client:                 
    build:
      dockerfile: Dockerfile.dev
      context: ./client    
    volumes:
      - ./client/:/app      
      - /app/node_modules  
    networks:
      - backend           
  server:                          
    build:
      dockerfile: Dockerfile.dev
      context: ./server    
    volumes:
      - ./server/:/app      
      - /app/node_modules      
    environment:
      - NODE_PATH=src
      - PORT=3050
      - DB_HOST=mongo
      - DB=test
      - REDIS_HOST=redis
      - REDIS_PORT=6379      
    networks:
      - backend  
    depends_on:
      - mongo
      - redis 
    ports:
      - "5000:3050"   
  redis:
    container_name: redis
    image: redis
    environment:
      - ALLOW_EMPTY_PASSWORD=yes
    networks:
      - backend 
    volumes:
      - data:/data/redis   
    ports:
      - "6379:6379"
    restart: always    
  mongo:
    container_name: mongo
    image: mongo
    volumes:
      - data:/data/db
    ports:
      - "27017:27017" 
    networks:
      - backend

  nginx:
    restart: always
    build:
      dockerfile: Dockerfile.dev
      context: ./nginx 
    ports:
      - '3000:80' 
    networks:
      - backend
    

networks: 
  backend:
    driver: bridge

volumes:
  data:
    driver: local  

위 내용을 잠깐 설명하자면 기본적으로 서비스는 5개를 실행하고 있다.

  • client
  • server
  • mongo
  • redis
  • nginx

이 많은 서비스를 저 파일 하나로 묶어서 배포를 할수 있다. 멋지다.

자 그럼 실행해서 잘되는 지 체크 해보자.

실행

# --build는 내부 Dockerfile 이 변경시 다시 컴파일 해준다. 
docker-compose up --build or docker-compose up

docker-compose stop or docker-compose down

일단 이상태로는 기본 환경만 올라간 상태라서 뭘 할수 있는 건 아니다.

소스를 좀 더 추가해서 연동을 해보자.

Client

  • App.js
    • npm install axios
import React, { Component } from "react";
import axios from "axios";
import Items from './Items';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      content: "",
      change: false
    };

    this.handleChange = this.handleChange.bind(this);
    this.sumbit = this.sumbit.bind(this);
  }

  handleChange(event) {
    const target = event.target;
    const value = target.value;
    const name = target.name;

    console.log(name, value);

    this.setState({
      [name]: value
    });
  }

  async sumbit(event){
    event.preventDefault();
    console.log(`${this.state.title},${this.state.content}`);

    await axios
      .post("/api/todos", {
        title: this.state.title,
        content: this.state.content
      })
      .then((result) => {
        this.setState({
          change : true
        });
      })
  }

  render() {
    return (
      <div style={{ margin: "100px" }}>
        <div>
          <form>
            <p>투두리스트</p>
            <input type="text" name="title" onChange={this.handleChange} />
            <br />
            <textarea name="content" onChange={this.handleChange} />
            <button onClick={this.sumbit}>전송</button>
          </form>
        </div>
        <Items change={this.state.change}/>
      </div>
    );
  }
}

export default App;
  
await axios
      .post("/api/todos", {
        title: this.state.title,
        content: this.state.content
      })

여기에서 /api를 통해서 바로 도커내 api서버로 접근을 할 수 있다.

그리고 추가된 리스트는 Items.js 컴포넌트를 만들어서 관리한다.

import React, { Component } from "react";
import axios from "axios";

class Items extends Component {
  constructor(props) {
    super(props);
    this.state = {
        Todos: []
    }
  }

  componentWillReceiveProps(props) {
      console.log(props);
      this.renderTodos();
  }

  componentDidMount() {
      this.renderTodos();
  }

  async renderTodos() {

    try {
        let todos = await axios.get("/api/todos");

        this.setState({
            Todos: todos.data.map(todo => {
                console.log(this.getItem(todo));
                return this.getItem(todo);
            })
        });
    } catch (err) {
        console.log(err);
    }
  }

  getItem(todo) {
    //   console.log(todo);
    return (
      <div key={todo._id}>
        <div style={{ padding: "10px", border:"1px solid red" }}>          
          <div>{todo.title}</div>
          <div>{todo.content}</div>
          <div>{todo.regdate}</div>
        </div>
      </div>
    );
  }

  render() {
    return <div>{this.state.Todos}</div>;
  }
}

export default Items;

결과 화면

image-20190119144313239

아주 심플하지만 작동은 잘 된다. 서버쪽 소스도 업데이트를 해야 잘 나오는 것 명심하자.

Server

Server에서 추가 사항은 몽고 디비와 연동해서 데이터 주고 받는 부분이다.

  • app.js
    • npm install mongoose
  var createError = require('http-errors');
  var express = require('express');
  var path = require('path');
  var cookieParser = require('cookie-parser');
  var logger = require('morgan');
  var cors = require('cors');
  var mongoose = require('mongoose');
  
  var db = mongoose.connection;
  db.on('error', console.error);
  db.once('open', function(){    
      console.log("Connected to mongod server");
  });
  
  mongoose.connect('mongodb://mongo/todo');
  
  var indexRouter = require('./routes/index');
  var usersRouter = require('./routes/users');
  var todoRouter = require('./routes/todo');
  
  var app = express();
  
  //Cors 설정
  app.use(cors());
  
  // view engine setup
  app.set('views', path.join(__dirname, 'views'));
  app.set('view engine', 'jade');
  
  app.use(logger('dev'));
  app.use(express.json());
  app.use(express.urlencoded({ extended: false }));
  app.use(cookieParser());
  app.use(express.static(path.join(__dirname, 'public')));
  
  app.use('/', indexRouter);
  app.use('/users', usersRouter);
  app.use('/todos', todoRouter);
  
  // catch 404 and forward to error handler
  app.use(function(req, res, next) {
    next(createError(404));
  });
  
  // error handler
  app.use(function(err, req, res, next) {
    // set locals, only providing error in development
    res.locals.message = err.message;
    res.locals.error = req.app.get('env') === 'development' ? err : {};
  
    // render the error page
    res.status(err.status || 500);
    res.render('error');
  });
  
  module.exports = app;
  
  • todo.js
var express = require('express');
var router = express.Router();

var Todo = require('../models/todo');

/* GET home page. */
router.get('/', function(req, res, next) {
  return Todo.find({}).sort({regdate: -1}).then((todos) => res.send(todos));
});

router.post('/', function(req, res, next) {
  const todo = Todo();
  const title = req.body.title;
  const content = req.body.content;

  todo.title = title;
  todo.content = content;

  return todo.save().then((todo) => res.send(todo)).catch((error) => res.status(500).send({error}));
});

module.exports = router;
  • model/todo.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var todoSchema = new Schema({
    content: String,
    title: String,
    isdone : Boolean,
    regdate: { type: Date, default: Date.now  }
});

module.exports = mongoose.model('todo', todoSchema);

마무리

다시 docker-compose up --build로 통해서 올려보면 작동이 잘 되는 걸 볼수 있다.

전체 소스는 여기에서 받을수 있다.

이상으로 GDG 부산에서 진행하는 도커 & 쿠버네티스 스터디 내용이었습니다.

참가해주셔서 감사합니다.

Flutter + Steho 사용하기

Flutter 로컬 디비 사용시 대중적인 방법은 sqflite 일것이다.

일반 sqlite 와 문법이 똑같으며 사용하기에도 편하다.

sqlite 를 모바일에서 연동시 페이스북에서 나온 steho 라는 라이버러리가 있다.

안드로이드만 현재 가능하지만 꽤 유용하게 사용가능하다.

그럼 flutter 에서 어떻게 적용하는지 살펴보자.

우선 sqflite 를 import 로 가져오고 생성시 getDatabasePath() 로 가져올 수 있다.

  Future<Database> init() async {
    String documentsDirectory = await getDatabasesPath(); //주의하자.
    final path = join(documentsDirectory, "test.db");
    final db = await openDatabase(
      path,
      version: _version,
      onCreate: _onCreate,
      onUpgrade: (Database db, int oldVersion, int newVersion) async {
        if(newVersion > oldVersion) {
          await File(path).delete();
          _onCreate(db, newVersion);
          print('onUpgrade done');
        }
      }
    );
    return db;
  }

그리고 stetho 라이버러리를 설치를 하자.

마지막으로 main.dart 에서 적용하면 된다.

  if(isInDebugMode) {
    Stetho.initialize();
  }

중요한 점은 디버깅 모드일 경우에만 실행을 해야 한다.

그래서 isInDebugMode 변수를 지정했다.

bool get isInDebugMode {
  bool inDebugMode = false;
  assert(inDebugMode = true);
  return inDebugMode;
}

크롬에서 chrome://inspect/#devices 주소를 열어서 왼쪽 Devices 항목을 클릭시 inspect 항목이 나오는데 그걸 클릭시 새 창이 뜨면서 확인이 가능하다.

Android 만 가능한걸로 안다. 그 점을 유의하자.

 

이상으로 Flutter + Sqflite + Stetho 적용법을 알아보았다.

 

2018년 회고

서론

나에게 2018년에 있어서는 정말 소중하고 열심히 살았던 해였던 것 같다. 나름 많은 스터디와 새로운 커뮤니티 활동등을 통해서 많은 분들과 인연을 맺었으며 전공 공부도 덩달아 많이 했던 시기였던 것이다.

커뮤니티 행사

부산에는 지난 2년간 큰 개발자 행사가 열리지는 않았던 것 같다. 이 점을 상당히 안타깝게 생각하는 중 나에게 GDG 부산 운영자를 할 수 있는 기회가 오게 되었다. 누구 하나 나서야 하는 상황에서 일단 나라도 시작을 했다.

  • 부산 Google IO Extended 2018 (8월) - 70명

  • 부산 GDG Devfest 2018 (11월) - 300명

  • 부산 Android 라이트닝 토크 (9월) - 30명

  • 부산 Flutter 스터디 잼

  • 부산 Tensorflow 스터디 잼

이 많은 행사를 우리 GDG 맴버들이 해낸것이다. 자랑스럽게 생각한다.

2019년도에도 더 열심히 활동해서 부산 개발자 커뮤니티 발전에 도움이 되었으면 한다.

스터디

수도권이 아닌 지방이라서 그런지 개발 스터디가 너무 없다. 그래서 나도 공부도 할 겸 스터디를 시작하였다. 내가 공부한 내용을 공유하는 강의식도 있었고 같이 공부하는 스터디도 있었다.

일주일에 4일 이상을 진행을 했었고 지금도 진행중이다. 힘들긴 해도 돌아보면 정말 많은 걸 배우고 보람이 있는 시간이었다.

2018년도는 블록체인을 좀 많이 공부했다.

  • 안드로이드 스터디

    • 배달앱 클론 강의식 스터디( Android + MVP + RXJAVA + Retrofit)

    • 안드로이드 프로젝트 스터디

  • 머신러닝

    • 모두의 딥러닝 ( 인프런 )

    • 텐서플로JS 스터디 (유데미)

  • 블록체인

    • 바닥부터 자바스크립트로 배우는 블록체인 코어 스터디

    • 알트 코인 개발 스터디

    • 블록체인 코어 개발 스터디

    • 이더리움 DAPP 개발 스터디

    • 고랭으로 구축하는 블록체인

    • Advance CSS 스터디

  • Flutter ( 요즘 내가 완전 빠져 있는 모바일 플랫폼)

    • Flutter 입문 스터디

    • Flutter 프로젝트 스터디

      • 미니 유튜브 클론

      • 미니 채팅앱 클론

    • Flutter 강의식 스터디 (진행중)

  • 인프라

    • 도커로 이용해서 CI / CD 스터디

      • AWS Beanstalk 배포

      • 도커라이징하는 방법 스터디

      • Dockfile 작성 공부

    • 쿠버네티스 입문 스터디

      • minikube 통해서 로컬 배포

      • pod 개념 스터디

      • GCP 에 배포

      • helm 적용

      • ingress nginx 적용

개발 스터디를 2018년에 대략 50~70번 넘게 진행하면서 느낀 점은 대략 이렇다.

  • 맴버들이 바빠서 공부를 해서 발표하는 것이면 서로 좀 힘들고 지속되기 힘들다.

  • 같이 영상을 보는게 집중도 잘 되고 서로 이야기도 좀 하면서 유익한 스터디가 될 수 있다.

  • 강의식으로 진행시 나에게도 한번 더 정리할 기회가 되므로 유익하다.

  • 꾸준하게 진행하는 게 정말 중요하다.

    • 다른 스터디들 다녀보니 중간에 파토 나는 경우가 많은데 이유는 장소도 문제이지만 사람이 점점 줄어든다. 그래서 내가 진행하는 스터디는 특별한 이유 없이시는 중단 하지 않는다.

    • 비용까지 받아서 책임감이 생겨서 그런지 꾸준히 하게된다..;;

물론 반성하는 것도 있다.

  • 좀 더 매력적인 스터디를 만들도록 노력해야 겠다.

  • 깊이 있는 스터디를 준비해서 진행한다. ( 문제는 신청하는 사람이 없다..ㅠㅠ)

  • 스터디 장소 문제 (조금 외곽이라서 ㅠㅠ)

올해는 머신러닝에 좀 더 집중해서 진행할 예정이다. 이미 생활패턴이 스터디에 적응되서 일주일에 몇번 하든 크게 문제는 없을 것 같다.

인증 공부

온라인에서 마음 맞는 분들과 다양한 전공 공부를 하고 있다.

2019년에는 현재 3기가 진행중이다. 이미 많은 분들이 2기까지 무사히 끝내신 분들이 많다.

100일동안 꾸준히 전공공부를 한다는 건 생각보다 쉽지 않은 편이다.

그래도 같이 공부하고 서로 격려하면서 쉽지 않은 길을 가니 끝까지 갈 수 있는 것 같다.

현재 3기는 약 50일까지 진행되었는데 그 내용은 한번 정리를 해볼까 한다.

현재 공부한 시간은 7,910분을 공부했으며 51일차에 들어갔다.


결론

2018년에는 주로 모바일, 블록체인 쪽을 많이 공부했던 것 같다. 2019년에는 머신러닝Flutter 에 좀 더 집중해서 공부할려고 한다.

블로그 정리도 12월에는 좀 더디게 진행 했는데 다시 마음 잡고 정리를 하면서 복습도 같이 하도록 하겠다.

이상으로 2018년을 마무리하면서 회고란걸 적어보았다.

올 한해도 화이팅하자!!

Flutter Codelab을 돌려볼때 오류가 발생될 경우가 있다. 

코드랩 위치 : https://codelabs.developers.google.com/codelabs/mdc-101-flutter


오류 내용은 No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android

해결방법은 2가지를 체크 해야 한다. 

1. android 폴더내에 project 레벨에서 build.gradle 에서 dependencies 가 3.1 이상으로 되어있는 지 체크하자. 

dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}

2. android/gradle/wrapper 폴더내 gradle-wrapper.properties 에 4.6으로 설정

distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip


이 두가지를 설정시 별 문제 없이 빌드 되는 걸 볼수 있다. 

이상으로 해결방법에 대해 공유해본다. 

+ Recent posts