터칭 데이터

Docker & K8S - 다수의 Container로 구성된 소프트웨어 실행 본문

Docker & K8S

Docker & K8S - 다수의 Container로 구성된 소프트웨어 실행

터칭 데이터 2023. 12. 20. 16:19

 

 

다수의 Container로 구성된 소프트웨어 실행

 

앞서 만들어본 Hangman 프로그램은 하나의 Container로 구성되어 있었는데 만일 다수의 Container로 구성된 프로그램이라면 어떻게 이미지를 빌드해야할까?

 

 

Docker에서 5개의 컨테이너로 구성된 프로그램을 실습용으로 제공하는데 이것으로 실습을 진행하려 합니다.

 

 

 

 

 

 

 

 

 

 

Voting application

첫 번째 실습은 Docker에서 제공하는 voting application을 메뉴얼하게 실행하고

 

두 번째 실습은 Docker Compose를 사용하여 더 편하게 실행해보겠습니다.

 

 

 

 

 

 

 

 

 

 

우리가 실행해볼 프로그램 설명

 

Docker에서 제공해주는 예제 프로그램 - Voting application

 

 

좌측과 같이 5개의 컨테이너로 이루어져 있는 프로그램입니다.

 

우측과 같은 최종 결과를 result-app에서 보실 수 있습니다.

 

 

1. voting-app에서 투표를 하고

 

2. redis라는 in-memory DB에 투표 입력이 들어가고

 

3. .NET이라는 Worker가 지켜보고 있다가

 

4. PostgreSQL에 적재하고

 

5. result-app이라는 NodeJS가 DB를 읽어 투표 결과를 보여줍니다.

 

 

 

 

 

 

 

 

 

 

 

예제 프로그램 다운로드 받기

 

Docker에서 제공해주는 예제 프로그램 - Voting application

 

git clone https://github.com/dockersamples/example-voting-app

 

코드 살펴보기

5개의 컨테이너로 이루어져 있고 Github에서 확인해보면 result, vote, worker 3개의 디렉토리가 존재하고 각각의 디렉토리 안에는 Dockerfile이 있습니다 이를 이용해서 3개의 컨테이너를 이미지로 빌드할 것입니다.

 

왜 5개중에 3개만 빌드하는지는 아래에서 설명드리겠습니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

먼저 매뉴얼하게 하나씩 빌드해보자

 

docker build -t vote ./vote

 

docker build -t result ./result

 

docker build -t worker ./worker

 

 

docker images

3번의 빌드가 끝났다면 위에서 만든 3개의 이미지들을 확인하기

 

 

5개 컨테이너인데 vote, result, worker 3개의 이미지만 빌드한 이유는

redis와 postgres는 공식 이미지들이라 Dockerfile로 빌드할 필요가 없음

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

먼저 매뉴얼하게 하나씩 실행해보자

 

docker run -d --name=redis redis

 

docker run -d -e POSTGRES_PASSWORD=postgres --name=db postgres

 

redis와 postgres는 공식 이미지이므로 Dockerfile로 Image를 빌드할 필요가 없습니다.

 

 

 

docker run -d --name=vote -p 5001:80 vote

 

docker run -d --name=result -p 5002:80 result

 

docker run -d --name=worker worker 

worker는 vote, result와 다르게 외부에서 접근할 필요가 없으므로 -p로 포트 맵핑을 하지 않았습니다.

 


이렇게 내가 일일히 하나씩 실행하면 동작할까?
각 컴포넌트들간의 네트워크 연결이 안되고 있음! 

 

 

 

위에서 살펴본 그림으로 설명하자면 좌측 흐름도의 화살표가 제대로 이어지지 않았다고 보시면 됩니다.

 

각 컨테이너들이 별도의 분리된 공간에서 실행되므로 다른 컨테이너들의 존재를 모르기 때문입니다.

 

 

 

 

 

 

 

 

 

 

 

 

결국 네트워크와 관련된 Docker 기능을 사용해야 합니다.

네트워크 관련 이슈를 더 자세히 보자

 

vote/app.py

def get_redis():
    if not hasattr(g, 'redis'):
    g.redis = Redis(host="redis", db=0, socket_timeout=5)
    return g.redis

 

투표를 위한 vote/app.py를 보면 Redis(host="redis", ...)와 같이 host를 지정하기는 했는데 redis가 무엇인지 아직은 모르기 때문에 네트워크가 연결되지 않고 있습니다.

 

vote에 로그인해서 iputils-ping 설치  ping 명령으로 redis 호스트 이름이 연결되는지 확인

$ ping redis
ping: cannot resolve redis: Unknown host


ping redis를 해보자 redis가 무엇인지 모른다는 결과가 출력됩니다. ping 기능으로 host 존재 여부를 파악하는지 확인할 수 있습니다. 그런데 ping은 default로 설치되어 있지 않기 때문에 root 유저로 vote에 로그인해 apt 명령어로 iputils-ping을 설치를 먼저 해줘야 합니다.

 

 

 

 

 

result/server.js

var pool = new pg.Pool({
    connectionString: 
    'postgres://postgres:postgres@db/postgres'
});

 

투표결과를 보여주는 result 앱입니다.

 

postgres와 연결하기 위해 'postgres://postgres:postgres@db/postgres' 적어줬는데 @다음에 오는 것은 관계형 DB 호스트의 이름입니다. db가 호스트 이름이며 postgre Docker Container의 이름이기도 합니다.

 

주황색 postgres는 postgres의 ID, 녹색 postgres는 패스워드입니다. 그렇게 연결하려는 db가 postgres가 되는 것입니다.

 

Postgres 연결시 postgres:postgres를 사용하고 있음을 주의깊게 볼것!

 

이번에 이 세팅은 해보지 않겠습니다만 방법을 알려드리자면 postgres Docker Container에 sh로 로그인 해 postgres와 관계된 shell을 띄우고 그곳에서 postgres DB를 만들고 psotgres ID와 PWD를 갖는 어카운트를 만들면 될 것입니다.

 

 

 

worker/Program.cs

var pgsql = OpenDbConnection("Server=db;Username=postgres;Password=postgres;");
var redisConn = OpenRedisConnection("redis");

 

worker는 redis에서 투표 결과를 가져와 postgres에 적재해야 하므로 redis와 db 모두 연결을 해야 합니다.

 

 

 

 

 

 

 



 

 

 

 

 

 

 

 

 

 

 

어떻게 Network 이슈를 해결할 수 있을까?

 

docker의 network 기능 사용

전에는 docker run의 link 옵션을 사용했지만 이제는 번거로워서 사용되지 않음

 

 

 

그렇게 network이라는 새로운 개념이 탄생

network을 하나 만들고 모든 컨테이너들을 이 네트워크 안으로 지정

후에 docker-compose로 만들어진 예를 통해 실행해보겠지만 그 때는 2개의 network인 back-tier, front-tier로 만들어 연결할 겁니다.

 

연결 상황에 따라 별개의 네트워크를 만들고 사용도 가능함

    - back-tier: voting, result
    - front-tier: redis, postgres, worker

 

 

하지만 매뉴얼 예제에서는 mynetwork을 하나를 만들고 5개 모든 컨테이너를 넣어서 진행할 예정

 

 

 

 

 

 

 

 

 

 

 

 

 

 

docker network create

 

docker container rm -f $(docker container ls -aq)

기존의 모든 컨테이너들 클린업

 

docker network create mynetwork

mynetwork라는 network 생성

 

 

 

이제 이하 5개의 컨테이너들을 mynetwork에 배정

docker run -d --name=redis --network mynetwork redis

 

docker run -d --name=db -e POSTGRES_PASSWORD=password --network mynetwork postgres

 

docker run -d --name=vote -p 5001:80 --network mynetwork vote

 

docker run -d --name=result -p 5002:80 --network mynetwork result

 

docker run -d --name=worker --network mynetwork worker

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

실습

 

 

Git clone

 

적당한 위치에 폴더를 생성합니다.

 

 

그리고 그 디렉토리로 이동하여 git clone https://github.com/dockersamples/example-voting-app을 해줍니다.

 

 

 

 

git clone https://github.com/dockersamples/example-voting-app 명령

PS D:\Dev_KDT\voting-app> git clone https://github.com/dockersamples/example-voting-app
Cloning into 'example-voting-app'...
remote: Enumerating objects: 1132, done.
Receiving objects:  98% (1110/1132)d 0 (delta 0), pack-reused 1132
Receiving objects: 100% (1132/1132), 1.17 MiB | 12.65 MiB/s, done.
Resolving deltas: 100% (435/435), done

 

 

 

 

 

 

위와 같이 클론이 되었습니다. 위의 디렉토리로 이동합니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

3개의 컨테이너 빌드

 

vote, result, worker 3개의 Image를 Dockerfile을 이용해 컨테이너로 빌드합니다.

나머지 2개 redis와 postgres는 공식 이미지가 존재하므로 Dockerfile로 빌드할 필요가 없다고 했었죠?

 

PS D:\Dev_KDT\voting-app\example-voting-app> docker build -t vote ./vote
(생략)

PS D:\Dev_KDT\voting-app\example-voting-app> docker build -t result ./result
(생략)

PS D:\Dev_KDT\voting-app\example-voting-app> docker build -t worker ./worker
(생략)

 

3개의 이미지를 빌드했습니다.

 

 

 

 

 

 

docker images

PS D:\Dev_KDT\voting-app\example-voting-app> docker images
REPOSITORY                                       TAG          IMAGE ID       CREATED              SIZE
worker                                           latest       abcd12345678   13 seconds ago       194MB
result                                           latest       abcdefghijk1   About a minute ago   224MB
vote                                             latest       12345678abcd   3 minutes ago        154MB

 

3개의 Images가 조회됩니다.

 

 

 

 

 

 

 

 

 

 

redis와 postgres Docker Container 하나씩 실행

 

PS D:\Dev_KDT\voting-app\example-voting-app> docker run -d --name=redis redis
~~

PS D:\Dev_KDT\voting-app\example-voting-app> docker run --name=db postgres

 

redis는 -d 붙여 background로 Container를 run 실행했습니다.

 

그런데 posgres는 보여드릴 에러가 있고 에러를 쉽게 확인하기 위해 -d를 붙이지 않고 foreground로 실행했습니다.

 

 

 

 

 

Error: Database is uninitialized and superuser password is not specified.
       You must specify POSTGRES_PASSWORD to a non-empty value for the
       superuser. For example, "-e POSTGRES_PASSWORD=password" on "docker run".

       You may also use "POSTGRES_HOST_AUTH_METHOD=trust" to allow all
       connections without a password. This is *not* recommended.

       See PostgreSQL documentation about "trust":
       https://www.postgresql.org/docs/current/auth-trust.html

 

docker run을 할 때 패스워드 세팅을 해달라는 뜻입니다. 앞에서 보셨죠?

 

 

 

 

 

 

docker run -d -e POSTGRES_PASSWORD=(원하는 패스워드) --name=db postgres

PS D:\Dev_KDT\voting-app\example-voting-app> docker run --name=db -e POSTGRES_PASSWORD=(원하는 패스워드) postgres
docker: Error response from daemon: Conflict. 
The container name "/db" is already in use by container "~~".
You have to remove (or rename) that container to be able to reuse that name.
See 'docker run --help'.

 

-e 옵션에 POSTGRES_PASSWORD로 패스워드를 붙여서 다시 postgres이미지를 run 해줍니다.

 

그런데도 에러가 나는데 그 이유는 아까 -e 옵션을 주지 않고 에러를 마주칠 때 생성한 db라는 이름의 컨테이너가 이미 stopped status 상태에서 db이름을 사용하고 있기 때문입니다.

 

 

 

 

 

PS D:\Dev_KDT\voting-app\example-voting-app> docker ps -a
CONTAINER ID   IMAGE                                                       COMMAND                   CREATED          STATUS                      PORTS      NAMES
abcdefg12345   postgres                                                    "docker-entrypoint.s…"   12 minutes ago   Exited (1) 12 minutes ago              db

PS D:\Dev_KDT\voting-app\example-voting-app> docker rm db
db

 

db 이름으로 exited 상태입니다.

 

 docker rm db(컨테이너 ID도 가능)로 삭제합니다.

 

 

 

 

 

 

docker run -d -e POSTGRES_PASSWORD=(원하는 패스워드) --name=db postgres

PS D:\Dev_KDT\voting-app\example-voting-app> docker run -d --name=db -e POSTGRES_PASSWORD=(원하는 패스워드) postgres

 

다시 postgres를 설치합니다. -d 옵션으로 실행하시거나 아니면 터미널 창을 하나 더 여시고 진행하시면 됩니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

vote, result, worker들의 Docker Container run

 

 

docker run -d --name=vote -p 5001:80 vote

PS D:\Dev_KDT\voting-app\example-voting-app> docker run -d --name=vote -p 5001:80 vote
~~

PS D:\Dev_KDT\voting-app\example-voting-app> docker ps
IMAGE      COMMAND                   CREATED          STATUS          PORTS                  NAMES
vote       "gunicorn app:app -b…"   37 seconds ago   Up 36 seconds   0.0.0.0:5001->80/tcp   vote
postgres   "docker-entrypoint.s…"   2 minutes ago    Up 2 minutes    5432/tcp               db
redis      "docker-entrypoint.s…"   22 minutes ago   Up 22 minutes   6379/tcp               redis

 

vote를 run하고 docker ps를 해보니 

 

vote와 그 전에 설치한 postgres, redis도 잘 조회됩니다.

 

 

 

 

 

docker run -d --name=result -p 5002:80 result

docker run -d --name=worker worker

PS D:\Dev_KDT\voting-app\example-voting-app> docker run -d --name=result -p 5002:80 result
~~

PS D:\Dev_KDT\voting-app\example-voting-app> docker run -d --name=worker worker
~~

 

 

 

 

PS D:\Dev_KDT\voting-app\example-voting-app> docker ps
IMAGE      COMMAND                   CREATED              STATUS              PORTS                  NAMES
worker     "dotnet Worker.dll"       2 seconds ago        Up 1 second                                worker
result     "/usr/bin/tini -- no…"   About a minute ago   Up About a minute   0.0.0.0:5002->80/tcp   result
vote       "gunicorn app:app -b…"   5 minutes ago        Up 5 minutes        0.0.0.0:5001->80/tcp   vote
postgres   "docker-entrypoint.s…"   6 minutes ago        Up 6 minutes        5432/tcp               db
redis      "docker-entrypoint.s…"   27 minutes ago       Up 26 minutes       6379/tcp               redis

 

docker ps로 모든 컨테이너가 실행 중입니다.

 

하지만 아직 컨테이너들 사이에 네트워크로 연결되지는 않은 상태입니다.

 

 

 

 

 

 

 

 

 

네트워크 연결

 

 

접속은 정상적으로 되는 것 같지만 선택지를 누르면 Internal Server Error가 발생합니다.

 

vote 컨테이너가 redis와 네트워크로 연결되지 않았기 때문입니다.

 

 

 

 

 

 

 

포트 5002로 result에 접속하면 겉보기에는 제대로 동작하는 것 같지만 postgres에서 데이터를 받아오는 것은 아니기 때문에 정상 작동 상태는 아닙니다.

 

 

 

 

 

 

네트워크 연결 확인을 위해 ping이 필요합니다.

 

ping

PS D:\Dev_KDT\voting-app\example-voting-app> ping www.google.com

Ping www.google.com [142.250.206.196] 32바이트 데이터 사용:
142.250.206.196의 응답: 바이트=32 시간=29ms TTL=57
142.250.206.196의 응답: 바이트=32 시간=29ms TTL=57
142.250.206.196의 응답: 바이트=32 시간=29ms TTL=57
142.250.206.196의 응답: 바이트=32 시간=29ms TTL=57

142.250.206.196에 대한 Ping 통계:
    패킷: 보냄 = 4, 받음 = 4, 손실 = 0 (0% 손실),
왕복 시간(밀리초):
    최소 = 29ms, 최대 = 29ms, 평균 = 29ms

 

ping은 위와 같이 연결이되는 안되는지 확인할 수 있습니다.

 

vote 컨테이너에 root 유저로 로그인해 ping을 설치하고 사용하겠습니다.

 

 

 

 

 

 

 

 

root 유저 로그인, apt update, ping 설치

PS D:\Dev_KDT\voting-app\example-voting-app> docker exec -it --user root vote sh
# ping redis
sh: 1: ping: not found

# apt update
(생략..)

# apt install iputils-ping
(생략..)

 

 

 

 

 

# ping redis
ping: redis: Name or service not known

 

현재 vote는 redis를 알지 못한다고 합니다. network 커맨드로 network를 만들고 각 컨테이너들을 같은 network로 배치하겠습니다.

 

 

 

 

 

 

 

 

 

Network

 

먼저 기존에 설치한 5개의 모든 voting application 컨테이너들을 삭제합니다.

network 옵션을 붙여 주고 다시 생성해야하기 때문입니다.

 

PS D:\Dev_KDT\voting-app\example-voting-app> docker container rm -f (컨테이너 ID들)

 

 

 

 

 

mynetwork라는 network 만들기

PS D:\Dev_KDT\voting-app\example-voting-app> docker network create mynetwork

 

docker network create mynetwork 명령으로 mynetwork를 생성했습니다.

 

 

 

 

 

 

PS D:\Dev_KDT\voting-app\example-voting-app> docker run -d --name=redis --network mynetwork redis

PS D:\Dev_KDT\voting-app\example-voting-app> docker run -d --name=db -e POSTGRES_PASSWORD=(패스워드) --network mynetwork postgres

PS D:\Dev_KDT\voting-app\example-voting-app> docker run -d --name=vote -p 5001:80 --network mynetwork vote

PS D:\Dev_KDT\voting-app\example-voting-app> docker run -d --name=result -p 5002:80 --network mynetwork result

PS D:\Dev_KDT\voting-app\example-voting-app> docker run -d --name=worker --network mynetwork worker

PS D:\Dev_KDT\voting-app\example-voting-app> docker ps
IMAGE      COMMAND                   CREATED          STATUS          PORTS                  NAMES
worker     "dotnet Worker.dll"       4 seconds ago    Up 3 seconds                           worker
result     "/usr/bin/tini -- no…"   29 seconds ago   Up 28 seconds   0.0.0.0:5002->80/tcp   result
vote       "gunicorn app:app -b…"   30 seconds ago   Up 29 seconds   0.0.0.0:5001->80/tcp   vote
postgres   "docker-entrypoint.s…"   31 seconds ago   Up 30 seconds   5432/tcp               db
redis      "docker-entrypoint.s…"   31 seconds ago   Up 31 seconds   6379/tcp               redis

 

다시 5개의 Container를 만드는데 이 때 --network 옵션으로 방금 만들어 둔 mynetwork를 지정합니다.

 

정상적으로 docker ps로 5개 모든 컨테이너들이 조회됩니다.

 

 

 

 

 

 

 

 

 

결과 보기

 

 

vote의 5001에 접속해 강아지에 투표하니 제대로 체크가 됩니다.

 

 

 

 

 

 

 

네트워크가 제대로 설치되었는지 다시 vote에 ping을 설치하고 redis host를 찾아냈는지 확인하겠습니다.

 

PS D:\Dev_KDT\voting-app\example-voting-app> docker exec -it --user root vote sh

# apt update
(생략)

# apt install iputils-ping
(생략)

# ping redis
(생략)
64 bytes from redis.mynetwork (IP 주소): icmp_seq=28 ttl=64 time=0.056 ms
(생략)

 

정상적으로 작동합니다.

 

여러개의 Container로 구성된 프로그램을 network를 설정해 구동하는 방법을 실습해보았습니다.