
- 여러가지 언어를 사용하여 컨테이너가 어떻게 제작되는지 공부해보자.
- 다음 프로젝트에는 직접 한번 컨테이너를 제작하여 구현해보자.
참고해보면 좋은 자료: https://gompro.postype.com/post/1735800
Github 주소 출처: https://github.com/docker/awesome-compose/tree/master/nginx-nodejs-redis
도커 자료: https://docs.docker.com/samples/python/

compose.yml파일은 Redis, 두 개의 웹 서버(web1및web2), 그리고 Nginx를 설정합니다.
services:
redis: # redis 서비스(Redis 데이터베이스를 사용하는 서비스 정의)
image: 'redislabs/redismod'
ports:
- '6379:6379' # redis 이미지를 사용하여, 6379포트로 노출
web1:
restart: on-failure # Node.js 및 Express.js 애플리케이션을 실행하는 서버
build: ./web
hostname: web1
ports:
- '81:5000'
web2:
restart: on-failure
build: ./web
hostname: web2
ports:
- '82:5000'
nginx: # web1, web2 앞에 80번포트로 노출시켜 로드밸런싱 역할
build: ./nginx
ports:
- '80:80'
depends_on:
- web1
- web2 # depends_on을 사용하여 web1, web2 먼저 시작
- Dockerfile 은 Node.js 애플리케이션을 Docker 이미지로 빋드하기 위한 기본적인 구성이다. 이 구성을 사용하면 애플리케이션을 컨테이너화하고 다양한 환경에서 실행 가능이점이 있다
FROM node:14.17.3-alpine3.14 # docker이미지
WORKDIR /usr/src/app # 작업디렉토리 설정
COPY package.json package-lock.json ./# json 파일을 현재 디렉토리로 복사
RUN npm ci # package-lock.json 파일을 이용해 정확한 버전 설치
COPY ./server.js ./ # server.js` 파일을 작업 디렉토리로 복사
CMD ["npm","start"] # 애플리케이션 시작
- Node.js 웹 애플리케이션을 구성하는데 사용되는 npm 패키지 모음이다. 패키지들이 서로의존하여 웹이 기동가능하다. (패키지예: array-flatten, express, redis, unpipe등등)
{
"name": "web",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
" i n t e g r i t y " :
"sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHc
VF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"requires": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
}
},
,....... 너무길어서 생략
- server.js를 끌어와 web 테스트를 위한 파일
{
"name": "web",
"version": "1.0.0",
"description": "Running Node.js and Express.js on Docker",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.17.2",
"redis": "3.1.2"
},
"author": "",
"license": "MIT"
}
db인 Redis를 사용하여 사용자가 웹 페이지에 접속할 때마다 방문 횟수를 저장하고, 각방문에 대한 정보를 웹 페이지에 표시하는 서버를 만드는 파일(테스트 용도)
// 모듈 가져오기
const os = require('os');
const express = require('express');
const app = express();
const redis = require('redis');
// Redis 모듈을 통한 Redis 클라이언트 생성 및 연결
const redisClient = redis.createClient({
host: 'redis',
port: 6379
});
// get 요청에 대한 numVisits 키의 값을 가져와 Redis에 업데이트된 방문 회수 저장 응답
app.get('/', function(req, res) {
redisClient.get('numVisits', function(err, numVisits) {
numVisitsToDisplay = parseInt(numVisits) + 1;
if (isNaN(numVisitsToDisplay)) {
numVisitsToDisplay = 1;
}
res.send(os.hostname() + ': Number of visits is: ' + numVisitsToDisplay);
numVisits++;
redisClient.set('numVisits', numVisits);
});
});
// Express 애플리케이션을 5000번에 리스닝 설정
app.listen(5000, function() {
console.log('Web application is listening on port 5000');
});
nginx 구성 도커 파일
FROM nginx:1.21.6
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
upstream 로드 밸런스를 설정하여 (web1, web2)에 들어오는 트래픽을 분산시켜준다.
upstream loadbalancer {
server web1:5000;
server web2:5000;
}
server {
listen 80; # 80포트 요청 처리
server_name localhost; # localhost 도메인 처리 요청
location / { # 모든 경로 요청 아래 설정 적용
proxy_pass http://loadbalancer;
}
}
$ docker compose up –d
$ curl localhost:80
$ docker compose down

JDBC란? Java DataBase Connectivity의 약어로 Java와 DB연결을 위한 표WNS API다
자바에서 DB를 연결하고, 그 안에 있는 데이터를 가져오기 위해서 사용하는 API다
전체적인 구조

자바와 DB를 연결하기 위해서는 두 단계를 거쳐야한다.
JDBC API는 JDK에 포함되어있으며 DBMS에 상관없이 사용할 수 있는 API를 제공한다.
JDK에 이미 포함이 되어있기 때문에 따로 다운로드하거나 설치할 필요는 없다.
대부분의 api는 java.sql 패키지나 javax.sql 패키지에 있는 api를 사용한다.
주요 클래스 및 인터페이스로
• DriverManger : JDBC 드라이버 로드
• Connectoin : DB와 연결하기 위한 인터페이스
• Statement / PreparedStatement / CallableStatement (SQL을 보내기 위한 통
로)
• Statement : SQl을 보내기 위한 통로. 인자가 없음.
• PreparedStatement : Statement와 동일한데 차이점은 인자값으로 SQL을
받기 때문에 특정한 SQL에 대한 통로라고 생각하면 된다.
• CallableStatement : PL/SQL을 호출할 때 사용
• ResultSet : SQL문의 결과를 저장하는 객체
등이 있다.
JDBC Driver는 DBMS에 따라서 다르게 다운로드를 받아줘야한다. 각 DBMS측에서 제공해주
는 라이브러리 압축 파일로 필자는 MySQL을 사용하기 때문에 MySQL에서 다운을 받아줬다.
출처: https://yoon990.tistory.com/58[개발저장소:티스토리

- backend와 db컨테이너 두 개 서비스 정의
services:
backend:
build: backend
ports:
- 8080:8080
secrets:
- db-password
db:
# We use a mariadb image which supports both amd64 & arm64
image: mariadb:10.6.4-focal
# If you really want to use MySQL, uncomment the following line
# image: mysql:8.0.27
restart: always
secrets:
- db-password
volumes: # db의 데이터 보존을 위한 볼륨 매핑
- db-data:/var/lib/mysql
environment:
- MYSQL_DATABASE=example
- MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db-password
expose:
- 3306
- 33060
# db 데이터 저장을 위한 볼륨 생성
volumes:
db-data:
secrets:
db-password:
file: db/password.txt
- 다중 스테이지 빌드를 사용하면 최종 이미지 크기를 최적화하고, 빌드 도구 및 개발 도구와같은 불필요한 항목을 최종 이미지에 포함되지 않고 필요한 것만 포함하여 이미지를 더 가볍게 만들 수 있다.
# syntax=docker/dockerfile:1.4
FROM --platform=$BUILDPLATFORM maven:3.8.5-eclipse-temurin-17 AS build WORKDIR /workdir/server
# pom.xml의 파일을 내려받는다.
COPY pom.xml /workdir/server/pom.xml
RUN mvn dependency:go-offline
COPY src /workdir/server/src
# assembly:single 명령어를 통해 실행 가능한 jar 파일 생성
RUN mvn --batch-mode clean compile assembly:single
# 위에 빌드 이미지를 통해 개발자를 위한 환경 설정 만든다.
FROM build AS dev-envs
RUN <<EOF
apt-get update
apt-get install -y --no-install-recommends git
EOF
RUN <<EOF
useradd -s /bin/bash -m vscode
groupadd docker
usermod -aG docker vscode
EOF
# install Docker tools (cli, buildx, compose)
# docker 도구 복사
COPY --from=gloursdocker/docker /
# 실행 명령어
CMD ["java", "-jar", "target/app.jar" ]
# 앞서 생성된 JAR 파일을 복사하고 실행 명령어를 통해 실행
FROM eclipse-temurin:17-jre-focal
ARG DEPENDENCY=/workdir/server/target
EXPOSE 8080
COPY --from=build ${DEPENDENCY}/app.jar /app.jar
CMD java -jar /app.jar
- Spark java 웹 프레임워크를 사요하며, MySQL DB와 연동을 위해 JDBC 드라이버 사용
- Maven 어셈블리 플러그인을 사용하여 종속성을 포함한 단일 JAR파일 생성
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.company</groupId>
<artifactId>project</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>App</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<finalName>app</finalName>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.4</version>
</dependency>
</dependencies>
</project>
- SparkJava 웹 프레임워크를 사용하여 MySQL DB와 상호작용하는 간단한 백엔드를 제공
import com.google.gson.Gson;
import com.mysql.cj.jdbc.exceptions.CommunicationsException;
import static spark.Spark.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
# MySQL JDBC 드라이버를 등록
public class App {
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.cj.jdbc.Driver");
prepare();
port(8080);
get("/", (req, res) -> new Gson().toJson(titles()));
}
# titles 메소드를 통해 글의 제목을 가져와서 리스트로 반환, 데이터베이스 연결
private static List<String> titles() {
try (Connection conn = connect()) {
List<String> titles = new ArrayList<>();
try (Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery("SELECT title FROM blog")) {
while (rs.next()) {
titles.add(rs.getString("title"));
}
}
}
return titles;
} catch (Exception ex) {
return new ArrayList<>();
}
}
# 데이터베이스 초기화 필요한 작업
public static void prepare() throws Exception {
try (Connection conn = connect()) {
recreateTable(conn);
insertData(conn);
}
}
# 5개의 블로그 포스터 추가
private static void insertData(Connection conn) throws SQLException {
for (int i = 0; i < 5; i++) {
try (PreparedStatement stmt = conn.prepareStatement("INSERT INTO blog (title) VALUES (?);")) {
stmt.setString(1, "Blog post #" + i);
stmt.execute();
}
}
}
# db에 blog 테이블 생성
private static void recreateTable(Connection conn) throws SQLException {
try (Statement stmt = conn.createStatement()) {
stmt.execute("DROP TABLE IF EXISTS blog");
stmt.execute("CREATE TABLE IF NOT EXISTS blog (id int NOT NULL AUTO_INCREMENT, title varchar(255), PRIMARY KEY (id))");
}
}
# mysql 연결 시도 (연결에 60번까지 시도)
private static Connection connect() throws Exception {
for (int i = 0; i < 60; i++) {
try {
return DriverManager.getConnection("jdbc:mysql://db/example?allowPublicKeyRetrieval=true&useSSL=false", "root", Files.lines(Paths.get("/run/secrets/db-password")).findFirst().get());
} catch (CommunicationsException ex) {
Thread.sleep(1000L);
continue;
} catch (Exception ex) {
throw ex;
}
}
throw new RuntimeException("Unable to connect to MySQL");
}
}
$ docker compose up –d
$ curl localhost:80
$ docker compose down

Github 출처: https://github.com/docker/awesome-compose/tree/master/flask-redis

- Redis 데이터베이스와 Flask 웹 애플리케이션을 함께 실행하는 파일
- Flask 웹 애플리케이션은 Redis에 의존하므로, depends_on 명령어를 사용하여Redis 서비스가 먼저 시작되도록 한다.
services:
# redis 서비스
redis:
image: redislabs/redismod
ports:
- '6379:6379'
# redis 서비스
web:
build:
context: .
target: builder
# flask requires SIGINT to stop gracefully
# (default stop signal from Compose is SIGTERM)
stop_signal: SIGINT
ports:
- '8000:8000'
volumes:
- .:/code
depends_on:
- redis
- Multi-stage 빌드를 활용하여 불필요한 파일과 의존성을 최소화하며, 두 가지 스테이지를
통해 빌드 및 실행을 분리하고 개발환경을 설정한다. (python)
# syntax=docker/dockerfile:1.4
# 파이썬 빌드 이미지 선택
FROM --platform=$BUILDPLATFORM python:3.10-alpine AS builder
# 작업 디렉토리
WORKDIR /code
# txt파일 사용하여 /code로 설정
COPY requirements.txt /code
# pip를 사용하여 requirementx.txt에 있는 Python 설치
RUN --mount=type=cache,target=/root/.cache/pip \
pip3 install -r requirements.txt
COPY . /code
ENTRYPOINT ["python3"]
CMD ["app.py"]
FROM builder as dev-envs
# 필요한 패키지와 그룹, 사용자 설정
RUN <<EOF
apk update
apk add git bash
EOF
RUN <<EOF
addgroup -S docker
adduser -S --shell /bin/bash --ingroup docker vscode
EOF
# install Docker tools (cli, buildx, compose)
# 도커 도구 추가
COPY --from=gloursdocker/docker / /
- 파이썬 코드를 이용해 Flask 웹 애플리케이션을 정의하고, 해당 애플리케이션의 Redis를
사용하여 페이지 조회 횟수 추적 테스트 파일
# flask와 redis를 가져옴
from flask import Flask
from redis import Redis
# flask 애플리케이션 생성
app = Flask(__name__)
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello():
redis.incr('hits')
counter = str(redis.get('hits'),'utf-8')
# 페이지 조회 횟수 반환
return "This webpage has been viewed "+counter+" time(s)"
# 웹 서버를 0.0.0.0 주소와 8000포트로 실행한다.
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000, debug=True)
$ docker compose up –d
$ curl localhost:80
$ redis-cli 패키지 설치후 redis-cli –p 6379 -> monitor
$ docker compose down
-> 페이지를 들어갈때마다 횟수가 오른다.
