모각코 9회차(5.24)

기먼지·2023년 6월 5일
0

인생작

목록 보기
9/9

Docker

프론트엔드와 백엔드 구조

  1. 개발툴에서 작성한 코드를 가지고 라이브 서버(프론트엔드 서버)를 띄움

    (사용자가 브라우저에 해당하는 주소를 입력하게 되면, 프론트엔드 서버에 해당하는 파일들을 요청하여 가져와 브라우저에 화면을 그려줌)

  2. 브라우저에서 필요한 데이터를 가져오기 위해 백엔드 서버로 API를 요청함

    백엔드 서버는 데이터베이스에서 board(게시글) 정보를 저장하거나, 가져와 브라우저에 응답을 보내게 됨

데이터베이스 SQL vs NoSQL

Untitled

데이터베이스는 크게 표(Table) 형식으로 만들어진 SQL과 서류(Collection) 형식으로 묶여 관리되는 NoSQL이 있음

(SQL과 NoSQL은 데이터베이스 자체를 나타내는 것이 아니고 특정 유형의 데이터베이스와 상호 작용하기 위해 사용되는 프로그래밍 언어를 의미)

→ 편의상 관계형 데이터베이스 관리시스템에서 사용되는 SQL을 관계형 DB, 비관계형 데이터베이스 관리시스템에서 사용되는 NoSQL을 비관계형 DB의 의미로 사용

SQLNoSQL
스키마OX
수직적 확장가능가능
Oracle, MySQL, PostgresqlMongoDB

사용된 프로그래밍 언어로 데이터베이스에 접근하는 법에는 ORM(Object-Relational Mapper)와 ODM(Object-Document Mapper)이 있음

ORM은 테이블로 이루어진 데이터베이스를 다룰 때 사용하는 SQL을 다른 언어에서 쉽게 사용하도록 해줌

ODM은 문서(document)와 collection으로 이루어진 NoSQL 데이터베이스를 다룰 수 있게 해줌

Docker

도커는 개발 환경 요소들이 설치된 모습을 이미지로 저장하여 클라우드에 올림

이미지들이 서로 연결되어 동작하는 설정을 문서(Dockerfile)로 저장함

새로운 컴퓨터에 가서 복사한 문서의 내용대로 이미지를 다운로드하여 설치함

가상머신과 비슷하게 볼 수 있으며, 가상머신보다 훨씬 빠르고 자원을 효율적으로 사용함

도커에는 불필요한 추가적인 운영체제 설치가 필요없음

왼쪽이 가상머신, 오른쪽이 도커

왼쪽이 가상머신, 오른쪽이 도커

도커 허브에서는 npm을 다운로드 하는 것처럼 다른 사람들이 올려놓은 이미지를 다운로드 할 수 있음

또한, 하나의 컴퓨터에서 다른 환경의 여러 서비스를 실행해야 하는 경우, 컨테이너로 분리되어 있어 서로 독립되어 실행될 수 있음

sudo wget -qO- https://get.docker.com/ | sh
docker --version
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
sudo apt update
apt-cache policy docker-ce
sudo apt install docker-ce

docker 설치 후 ERROR: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock 발생하는 경우

/var/run/docker.sock 파일의 권한을 666으로 변경하여 그룹 내 다른 사용자도 접근 가능하게 변경함

sudo chmod 666 /var/run/docker.sock

도커 실행 명령어를 실행함

~~docker run ubuntu:22.04~~
❓ Docker를 사용하는 이유

새로운 컴퓨터를 샀거나 새로운 직원이 들어왔다고 가정하였을 때, 협업을 하기 위해 컴퓨터의 개발 환경을 모두 똑같이 맞춰야 함
이를 위해 Node.js와 같은 런타임 환경, 사용하는 언어, 데이터베이스 등의 버전을 맞춰 설치하여야 함

→ 이 과정을 간편하게 해주는 것이 docker

Dockerfile

컨테이너를 실행하기 전에 먼저 이미지를 만들어야 함

Dockerfile 이라는 이름의 파일을 만들고 이미지를 만들기 위한 docker build 명령어를 입력

가상 컴퓨터를 만들기 위해 설명서를 참고하는데, 이 역할을 하는 것이 Dockerfile

// Dockerfile

FROM node:14

COPY ./index.js /myfolder/

WORKDIR /myfolder/

CMD node index.js
CMD yarn dev

FROM 리눅스:최신 버전 은 리눅스의 최신 버전이 깔린 컴퓨터 한대가 만들어짐

FROM node:14는 node, npm, yarn이 모두 설치된 리눅스 컴퓨터가 하나 생김

COPY ./index.js /myfolder/index.js 파일에 있는 모든 소스 코드를 가상 컴퓨터 안의 myfolder 폴더로 복사하여 저장함

가상 컴퓨터 안에 해당 폴더가 존재하지 않는다면 폴더가 자동으로 생성됨

WORKDIR /myfolder/ 는 가상 컴퓨터가 만들어지면 이후 명령어를 실행할 작업 폴더를 지정해 줌

CMD node index.js 는 가상 컴퓨터 내부에 복사해 둔 파일을 실행함

CMD yarn devpackage.json 파일을 확인하여 실행 명령어를 입력하여 서버를 실행해 줌

docker build

만들어진 설명서를 가지고 하나로 묶여진 이미지를 만드는 것을 build 한다고 함

docker build .

생성된 이미지를 확인해 볼 수 있음

docker images

이렇게 한 번 이미지를 만들어 놓으면 언제 어디서든 똑같은 환경의 가상 컴퓨터를 만들 수 있음

docker run

만들어진 이미지를 사용하여 새로운 컴퓨터를 만들기 위해 이미지를 실행함

docker run 이미지_ID

이렇게 이미지를 실행시키면 가상 컴퓨터 하나가 만들어지며, 도커로 만들어진 가상 컴퓨터를 컨테이너라고 부름

아래 명령어를 통해 docker가 실행된 후 종료가 되었기 때문에 도커 프로그램이 존재하지 않는 것을 확인할 수 있음

docker ps

아래 명령어를 통해 종료된 컨테이너를 볼 수 있음

docker ps -a

종료된 컨테이너는 가지고 있을 필요가 없기 때문에 삭제 명령을 통해 지워줄 수 있음

docker rm 컨테이너_ID

컨테이너는 종료, 삭제되었지만 이미지까지 삭제된 것은 아니기 때문에, 만들어진 이미지로 언제든 컨테이너를 생성할 수 있음

.dockerignore

우리는 도커로 띄워지는 가상 컴퓨터의 환경에 최적화된 node_modules 폴더를 생성해줘야 함

이미지 빌드 시, 로컬 환경의 간섭을 받지 않도록 모든 소스 코드들이 복사된 후 모듈을 따로 설치하는 명령어를 추가해 줌

// Dockerfile

FROM node:14

WORKDIR /myfolder/
COPY . /myfolder/

Run yarn install
CMD yarn dev

Docker image를 build하게 되면 Dockerfile에서는 RUN 명령어까지 실행됨

CMD로 실행된 명령어는 docker run 이미지ID 명령어로 실제 도커 컨테이너를 띄울 때 실행됨

우리가 구현한 express 서버는 한번 실행되면 우리가 직접 종료하기 전까지 코드의 실행이 종료되지 않음

즉, Docker image를 build하는 과정에서 코드의 실행이 종료되지 않기 때문에 build 또한 종료되지 않음

이러한 이유로 인해 yarn dev 명령어는 CMD 키워드로 실행해줘야 함

로컬 폴더에 node_modules 폴더가 있을 시 함께 복사되기 때문에 이를 방지하기 위해하여 build시 해당 폴더를 무시하도록 설정함

// .dockerignore

node_modules

→ 도커에서 node_modules 폴더를 무시하여, COPY . /myfolder/ 명령어로 모든 소스코드를 복사하더라도 node_modules 폴더는 복사하지 않음

docker 내부 접속

생성한 도커 안으로 들어가보도록 함

bash 쉘로 컨테이너 내부에 들어감 → 도커에서 돌아가고 있는 가상 컴퓨터의 터미널로 들어감

docekr exec -it 컨테이너_ID /bin/bash

pwd 명령어로 현재 폴더의 위치를 확인하고, ls 명령어를 통해 모든 소스코드들이 잘 복사되었는지 확인할 수 있음

exit 명령어로 종료함

pwd
ls
exit

포트 포워딩

지금까지 yarn dev 명령어로 express 서버를 3000번 포트에서 실행하고 localhost:3000에 접속하면 문제가 없었음

그러나, Docker 환경에서는 내 컴퓨터 안에 도커라는 프로그램이 있고, 그 안에서 express 서버가 실행 중임

가상 컴퓨터 안에서는 3000번 포트로 잘 접속이 되지만, 내 컴퓨터에서는 3000번에 아무것도 존재하지 않아 요청 시 길을 잃어버리게 되어 접속할 수 없게 됨

Untitled

따라서, 가상 컴퓨터의 3000번과 내 컴퓨터의 포트를 연결해줘야 함 → 이것을 포트 포워딩이라고 부름

실행되고 있는 컨테이너를 멈추는 명령어는 아래와 같음

docker stop 컨테이너_ID

내 컴퓨터의 8000번 포트와 도커 컴퓨터의 3000번 포트를 연결해 줌

docker run -p 8000:3000 이미지_ID

Docker와 package.json

Docker는 build를 할 때, Dockerfile을 보면서 명령어를 한 줄씩 실행함

이때, 기존에 존재하는 image와 비교하여 소스코드 상에 변경된 점이 없으면 이전 build시에 생성해 둔 캐시를 가져와 사용하고, 아니라면 그 부분부터 명령어를 실행해 모두 새롭게 build 함

// Dockerfile

FROM node:14

WORKDIR /myfolder/
COPY . /myfolder/

Run yarn install
CMD yarn dev

만약 index.js 파일의 소스코드를 일부 수정하였다면, COPY . /myfolder/ 아래의 명령어들이 모두 새로 build에 활용되어 변경된 것이 없는 모든 모듈들 또한 RUN yarn install 명령어를 통해 다시 설치되게 함 → package.json 내용은 건드리지 않았는데 모두 다시 설치한다는 점이 비효율적!

모든 소스 코드를 복사하기 전에 먼저 package.jsonyarn.lock을 복사하도록 적어줌

그 후 모듈을 설치하고(RUN yarn install), 나머지 파일을 복사함(COPY . /myfolder/)

마지막으로 서버를 실행함(CMD yarn dev)

FROM node:14

WORKDIR /myfolder/
COPY ./package.json /myfolder/
COPY ./yarn.lock /myfolder/
RUN yarn install

COPY . /myfolder/
CMD yarn dev

이렇게 Dockerfile을 작성하게 되면, package.jsonyarn.lock 파일이 수정되지 않았다면 RUN yarn install까지는 기존 것을 그대로 쓰고, 밑에 실제로 소스코드가 고쳐진 부분만 새로 반영되어 실행됨

Docker-compose / MongoDB

MongoDB / Mongo

mongoDB를 설치하기 위한 공개 키를 가져옴

wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -

mongoDB는 리눅스 기본 패키지로 제공되지 않기 때문에 패키지 저장소 리스트에 추가적으로 저장소를 만들어 줌

아래의 명령어를 통해 우분투 버전을 확인하여 버전에 맞는 명령어를 입력함(22.04 기준)

lsb_release -dc

echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list

저장소가 추가되었다면 패키지 리스트를 업데이트 해줌

sudo apt-get update

최신 버전의 mongoDB 패키지를 설치함

sudo apt-get install -y mongodb-org

mongoDB를 실행하고 상태를 확인함

sudo systemctl start mongod
sudo systemctl status mongod

정상적으로 mongoDB가 실행되었다면 localhost:27017에 들어가서 확인할 수 있음

mongoDB는 기본적으로 port:27017이 고정임

root 사용자를 등록하여 비밀번호가 있어야 mongoDB에 접속할 수 있도록 설정함

아래 명령어를 통해 shell을 사용할 수 있음

mongosh

기본적으로 mongoDB에 admin이라는 DB가 존재함

아래 명령어를 통해 admin을 선택해 줌

use admin

아래 명령어를 통해 사용자를 등록해 줌

db.createUser({user:'root', pwd:'본인이 설정하고 싶은 비밀번호', roles:['root']})

아래 명령어를 통해 shell에서 나올 수 있음

exit

mongoDB를 재시작해 줌 → root 사용자와 비밀번호 설정을 적용하기 위함

sudo systemctl restart mongod

아래 명령어를 입력하여 생성한 root 사용자로 접속할 수 있음

mongosh -u root -p

MongoDB Compass

MongoDB Compass는 mongoDB용 GUI 데이터를 시각적으로 탐색하는 툴임

데이터베이스를 쉽게 조회하고 수정할 수 있음

mongoDB Compass 설치 후 URI 부분에 mongodb://localhost:27017(default)를 입력한 뒤, Connect 버튼을 눌러 연결함

mongodb:// : mongoDB에 연결

localhost:27017 : mongoDB 서비스 주소(포트)

MongoDB

mongoDB는 문서 지향 저장소를 제공하는 NoSQL 데이터베이스 시스템임

데이터를 Document라고 부르며, 데이터의 집합을 Collection이라고 함

스키마 제약 없이 자유롭고, BSON(Binary JSON) 형태로 각 문서가 저장되며 배열이나 날짜 등 기존 RDMS에서 지원하지 않던 형태로도 저장할 수 있기 때문에 관계를 연결하는 JOIN이 필요없이 한 문서에 이해하기 쉬운 형태 그대로 정보를 저장할 수 있음

문서 지향 데이터베이스로, 객체지향 프로글밍과 잘 맞고 JSON을 사용할 때 유용함

자바스크립트를 기반으로 하는 Node.js와 호환이 매우 좋기 때문에, Node.js에서 가장 많이 사용하는 데이터베이스임

  • JOIN이 없으므로 JOIN이 필요 없도록 데이터 구조화가 필요
  • 다양한 종류의 쿼리문을 지원(필터링, 수집, 정렬, 정규 표현식 등)
  • 관리의 편의성
  • 스미카 없는 데이터베이스를 이용한 신속 개발. 필드를 추가하거나 제거하는 것이 매우 쉬움
  • 쉬운 수평 확장성
  • 인덱싱 제공

SQL vs MongoDB

각각 mySQL과 mongoDB가 가장 대표적이며 사용되는 용어에 차이가 있음

Untitled

Untitled

mongoDB에서는 질의문이 모두 JSON(BSON) 객체로 표현됨

Local에서 mongoDB 연결

데이터베이스를 확인함

show databases;

생성한 프로젝트의 데이터베이스를 사용할 수 있으며 collection을 확인할 수 있음

db.콜렉션명.find() 명령어를 실행하면 MongoDB Compass로 생성한 데이터가 잘 들어가 있는 것을 확인할 수 있음

use 프로젝트명;
show collections;
db.콜렉션명.find()

exit 명령어를 통해 shell 밖으로 빠져나와도 실제 mongoDB 프로그램은 동작 중임

명령어를 통해 24시간 켜져있는 서비스를 종료시킬 수 있음

sudo systemctl stop mongod

mongoDB-community 서비스가 종료되었는지 확인할 수 있음

systemctl list-units --type=service

Docker-compose

Docker-compose란 여러 개의 컨테이너를 실행시키는 도커 어플리케이션을 정의하기 위한 툴임

기본적으로 YAML 파일을 사용하여 어플리케이션의 서비스를 구성할 수 있음

즉, YAML 파일로 여러 개의 docker 내부 속성을 설정하고 YAML 파일을 실행시켜 마치 docker를 일괄적으로 한 번에 실행하는 것과 같다고 생각하면 됨

Docker-compose를 활용한 mongoDB 연결

mongoDB를 연결하기 위해 mongoDB 컨테이너를 띄워야 함

Dockerfile.mongo 파일을 작성하여 mongo 버전을 5로 정의함

// Dockerfile.mongo

FROM mongo:5

우리는 DockerfileDockerfile.mongo를 통해 각각의 가상 컴퓨터를 만들었음

이때, 각자 다른 컴퓨터들을 연결하기 위해 포트포워딩을 해줘야 함

도커로 만들어진 각각의 다른 컴퓨터들의 포트포워딩을 위해 그룹핑하는 것을 Docker-Compose라고 함

express와 mongoDB의 서버를 한번에 실행시키기 위해 .yaml 파일을 정의해줘야 함

확장자는 .yml 이나 .yaml 둘 다 사용 가능함(들여쓰기에 민감하므로 주의)

// docker-compose.yaml

version: "3.7"

services:
  my-backend: 
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 3000:3000

  my-database:
    build:
      context: .
      dockerfile: Dockerfile.mongo
    ports:
      - 27017:27017

docker-compose.yaml 파일은 version, services, volumes, networks를 정의할 수 있음

services 정의는 각각의 컨테이너에 적용되는 configuration을 포함하고 있음

services 내부에 my-backendmy-database를 정의함(다른 변수명도 가능)

  • context: Dockerfile을 포함하는 디렉토리 경로 또는 git repo의 url
  • dockerfile: Dockerfile을 대체하는 파일을 지정

ports 정의는 Host OS와 컨테이너의 포트를 바인딩 시켜줌

(예를 들어, 컨테이너의 노출된 포트 3000을 Docker Host 컴퓨터의 포트 3000에 전달)

docker image를 생성한 뒤 container를 실행

docker-compose build

docker-compose up

Dockerfile.mongo로 정의해두었던 mongoDB의 설정을 Dockerfile.mongo를 거치지 않고 YAML 파일에서 image를 사용해 바로 정의할 수 있음

Dockerfile.mongo 파일을 삭제하고 docker-compose.yaml을 수정함

// docker-compose.yaml

version: "3.7"

services:
  my-backend: 
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 3000:3000

  my-database:
    image: mongo:5
    ports:
      - 27017:27017

YAML 파일에서 해당 image를 바로 가져올 수 있도록 image에 mongo 버전을 정의함

이제 Dockerfile.mongo 파일은 사용하지 않으므로 build 옵션은 제거해도 됨

ODM(Object Document Mapping) & Mongoose

Mongoose는 Node.js와 MongoDB를 위한 ODM(Object Data Mapping) 라이브러리임

호환성이 없는 JavaScript와 mongoDB의 데이터를 mapping하여 간편한 CRUD를 가능하게 해줌

아래 명령어로 라이브러리를 설치해 줌

yarn add mongoose
// index.js

// ...
import mongoose from 'mongoose';

// ...

// mongoDB 접속
mongoose.connect("mongodb://my-database:27017/mydocker");

// ...

앱이 하나의 DB만 사용하는 경우 mongoose.connect, 추가 연결을 생성해야 하는 경우 mongoose.createConnection을 사용함

mongoose.connect(’mongodb://localhost/mydocker’)의 형태로 작성됨

두 개의 도커 컨테이너를 구성했고, 각각 express 서버, mongoDB로 구성되어 있음

localhost가 아닌 docker-compose를 통해 함께 띄워진 mongoDB가 구동될 도커 컨테이너를 연결해 줘야 함

이때, docker-compose.yml 파일에 작성된 각 컨테이너명을 사용해 두 개의 컨테이너를 엮어줄 수 있음

이를 네임 리졸루션이라고 함

mongoose.connect("mongodb://my-database:27017/mydocker") 네임 리졸루션을 이용하여 mongoDB를 연결해 줌

모델을 정의하기 위해 board.model.js 파일을 작성함

import mongoose from 'mongoose'

const BoardSchema = new mongoose.Schema({
    writer: String,
    title: String,
    contents: String
})

export const Board = mongoose.model("Board", BoardSchema)

새로운 스키마를 BoardSchema 변수에 담아서 선언하고 내부에는 요소들의 key 값의 타입을 지정함

schema는 mongoDB 내에서의 collection을 의미한다고 생각하면 됨

model() 메서드를 사용하여 문자열과 schema를 전달하여 model을 생성함

첫번째 인자는 해당 collection의 단수적 표현을 나타내는 문자열임

생성된 model을 외부에서 접근할 수 있도록 export 해줌

GET 데이터 조회

// index.js

// ...
import { Board } from './models/board.model.js';

// ...

app.get('/boards', async (req, res) => {
    // 1. 데이터를 조회하는 로직 => DB에 접속해서 데이터 꺼내오기
    const result = await Board.find();

    // 2. 꺼내온 결과 응답 주기
    res.send(result);
});

Board model을 통해 mongoDB의 Board collection에 담긴 데이터들을 탐색하여 변수 result에 담아줌

find는 보통 모든 데이터를 조회할 때 사용하며, findOne은 특정 데이터만 조회할 때 사용

mongoose의 스키마는 mongoDB에 저장되는 document의 Data 구조, 즉 필드 타입에 관한 정보를 JSON 형태로 정의한 것

Untitled

POST 데이터 등록

app.post('/boards', async (req, res) => {
    console.log(req.body);

    // 1. 데이터를 등록하는 로직 => DB에 접속해서 데이터 저장하기
    const board = new Board({
        writer: req.body.writer,
        title: req.body.title,
        contents: req.body.contents,
    });
    await board.save();

    // 2. 저장 결과 응답 주기
    res.send('게시물 등록에 성공하였습니다!!');
});

POST 요청을 받을 때 JSON 데이터를 담은 BODY에서 각각의 요소를 하나씩 빼와 연결해 놓은 mongoDB의 boards collection에 저장

new Board 생성자를 통해 해당 model에 맞는 형식의 데이터를 생성

board.save()를 실행하면 실제 데이터베이스에 생성한 데이터가 저장됨

Docker-compose의 volumes

지금은 로컬에 작성된 파일을 변경해도 docker내의 파일은 변경되지 않음

즉, 소스코드를 수정할 때마다 매번 docker-compose build, docker-compose up을 실행해야 함

  1. 로컬에서 소스코드를 변경하면 docker내에서도 변화시키기
  2. docker내에서 nodemon으로 실행시키기

위 두가지를 적용하여 매번 새로운 image를 build 하지 않고 변경된 소스코드를 바로 서버에 적용시킬 수 있음

docker 내에 있는 서버는 이미 nodemon으로 실행되고 있기 때문에 1번 과정만 적용하면 됨

version: "3.7"

# 컴퓨터들
services:

  # 컴퓨터 이름
  my-backend: 
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./index.js:/myfolder/index.js
      - ./email.js:/myfolder/email.js
    ports:
      - 4000:4000

  # 컴퓨터 이름
  my-database:
    image: mongo:5
    ports:
      - 27017:27017

- ./index.js:/myfolder/index.js

콜론을 기준으로 좌측은 local에 있는 파일을, 우측은 docker 컨테이너 내부에 있는 파일을 가리킴

volumes는 양측에 있는 파일을 동기화시켜주는 옵션이기 때문에 좌측에 있는 파일(local에 있는 파일)에 변화가 생기면 우측(docker 컨테이너 내부에 동기화된 파일)도 변경시켜주는 역할을 함

이때, docker 내부에 띄워져 있는 서버는 nodemon을 활용하고 있기 때문에 내부 소스코드에 변화가 생기면 refresh

profile
열심히 굴러가기 !

0개의 댓글