지난 포스팅에 이어 NGINX docker image를 사용자의 요구에 따라 Customization 하는 과정을 정리해보았다. 여기서 사용한 HW는 2대이며 다음과 같이 설정되어 있다.
- HW1
- Hostname : browndwarf01
- IP Address : 16.8.35.228
- Descrption : NGINX Docker image를 통해 NGINX 실행.
- HW2
- Hostname : browndwarf02
- IP Address : 16.8.35.227
- Description : WEB Service 실행 후 8080 port로 expose.
1. Docker Volumn Mount
NGINX를 설치하면 /usr/share/nginx/html에 기본 web page를 위한 Resource들이 위치되어 있다. root URL을 호출하게 되면 이 디렉토리에 위치한 index.html이 불려지게 되는데 Docker Volumn Mount를 통해 NGINX docker image의 index.html 바꿔치기를 해보겠다.
아래와 같이 /usr/docker/nginx 디렉토리를 생성하고, 거기에 index.html을 생성한다.
[root@browndwarf01 /]# cd usr
[root@browndwarf01 usr]# mkdir docker
[root@browndwarf01 usr]# cd docker
[root@browndwarf01 docker]# mkdir nginx
[root@browndwarf01 docker]# cd nginx
[root@browndwarf01 nginx]# vi index.html
# vi 편집 ; index.html
<HTML>
<BODY>
<H2>Welcome. Browndwarf!!!</H2>
</BODY>
</HTML>
~
그리고 'docker run' command로 nginx docker image를 실행할 때 -v option을 이용해서 docker image 내부의 디렉토리와 host machine의 디렉토리를 mount 한다.
[root@browndwarf01 nginx]# docker run --name browndwarf-nginx -v /usr/docker/nginx:/usr/share/nginx/html:ro -p 80:80 -d nginx
5b09e9433329a3e05eaebb6b149ba2bfc71b9f540123014d20659470c73912c7
[root@browndwarf01 nginx]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5b09e9433329 nginx "nginx -g 'daemon of…" 12 seconds ago Up 10 seconds 0.0.0.0:80->80/tcp browndwarf-nginx
Browser에서 웹서버를 호출하면 <pic 1>과 같이 위에서 제작한 index.html이 호출되게 된다.
이미 실행되는 Container에 다른 Command를 실행하려면 'docker exec' command를 사용한다. ls command를 통해 Container 내부에 위치한 /usr/share/nginx/html 디렉토리의 내용을 확인하면 위에서 생성한 /usr/docker/nginx 디렉토리와 동일하다는 점을 알 수 있다.
[root@browndwarf01 nginx]# docker exec browndwarf-nginx ls -lR /usr/share/nginx/html
/usr/share/nginx/html:
total 4
-rwxr-xr-x. 1 root root 70 Aug 26 07:00 index.html
위에서 사용한 docker command 문법은 아래와 같다.
docker exec [OPTIONS] [CONTAINER ID | CONTAINER NAME] command [ARG...]
- docker exec ; Host에서 Conainter안에 Command를 실행
- OPTIONS : 생략 가능. 그러나 Container 내부에서 bash를 실행시킬 경우 표준 입력을 위한 -i option과 터미널을 위한 -t option이 필요하다.
- CONTAINER ID or CONTAINER NAME ; 현재 실행 중인 Container ID나 NAME
- command : Container에서 실행할 Command. 위에서는 ls command를 실행한 예이다.
- ARG : Command의 argument. 위에서는 ls command의 -lR option과 path를 Argument로 사용하고 있다.
2. Container Log 확인
Docker 공식 문서를 보면 별다른 Logging 설정이 없을 경우 stdout, stderr과 같이 Console에 출력되는 내용을 Container에서 Log로 관리하게 된다고 나와있다.
... By default, docker logs shows the command’s STDOUT and STDERR. ...
그리고, 좀 더 세밀하게 Logging을 관리하려고 하면 logging driver라 불리는 별도의 logging mechanism을 이용해야 하는데, 여기에 대해서는 공식 문서 설명을 확인하는 것을 권장한다.
NGINX는 access log와 error log 두 개의 Log를 관리한다. 그리고 NGINX의 Official한 Docker Image에서는이 두 개의 Log File이 아래와 같이 각각 stdout과 stderr로 Link되어 있다.
[dohoon@browndwarf01 systeminfo]$ docker exec -it nginx bash
root@01068e450f9f:/# cd /var/log/nginx/
root@01068e450f9f:/var/log/nginx# ls -la
total 0
drwxr-xr-x. 1 root root 41 Mar 4 17:31 .
drwxr-xr-x. 1 root root 19 Mar 4 17:31 ..
lrwxrwxrwx. 1 root root 11 Mar 4 17:31 access.log -> /dev/stdout
lrwxrwxrwx. 1 root root 11 Mar 4 17:31 error.log -> /dev/stderr
그래서 'docker logs' command를 통해 NGINX의 Log를 확인하면 access log와 error log를 같이 확인할 수 있게 된다.
[root@browndwarf01 nginx]# docker logs browndwarf-nginx
172.17.0.1 - - [26/Aug/2019:07:00:22 +0000] "GET /index.html HTTP/1.1" 200 71 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-"
172.17.0.1 - - [26/Aug/2019:07:00:34 +0000] "GET /index.html HTTP/1.1" 200 70 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-"
위에서 사용한 docker command 문법은 아래와 같다.
docker logs [OPTIONS] [CONTAINER ID | CONTAINER NAME]
- docker logs : Container에서 출력하는 Log를 확인. 기본적으로 Container의 표준 출력(stdout), 표준 에러(stderr) 내용을 확인
- OPTIONS : 생략 가능. (ex. -f : Log의 지속 출력 유지)
- CONTAINER ID or NAME : Target Container ID or Name
3. Conf 파일 변경을 통한 Reverse Proxy 구성
이번에는 NGINX 설정을 변경해 다른 Host에서 동작하는 Web Service로 연결하는 Reverse Proxy 구성을 해보았다. Reverse Proxy는 보안과 Load Balacing을 위해 외부에서 들어오는 Request를 받아서 적절한 Backend System으로 연결시켜주는 중계 서버이다. 대략적인 구조는 <pic 2>와 같다.
Web Service는 아래의 Code와 같이 'GET /api/heartbeat' API를 call했을 때 'I'm alived'라는 String을 Return하게 구성하였다.
Step 1. Web Service 구현 (by Spring Boot) 및 Test
@RestController
@RequestMapping(value= "/api")
public class SampleController {
...
@GetMapping(value = "/heartbeat")
public ResponseEntity<String> checkHeartBeat(){
return ResponseEntity.ok("I'm alived");
}
...
}
위의 Web Service를 HW2 Machine(hostname : browndwarf02)에서 구동 및 Test 했다.
[browndwarf@browndwarf02 ~]$ ifconfig
...
eno1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 16.8.35.227 netmask 255.255.255.0 broadcast 16.8.35.255
inet6 fe80::9cac:71ab:7ae:c786 prefixlen 64 scopeid 0x20<link>
ether 70:5a:0f:46:79:38 txqueuelen 1000 (Ethernet)
RX packets 179668782 bytes 22818821114 (21.2 GiB)
RX errors 0 dropped 2139826 overruns 0 frame 0
TX packets 1074210 bytes 378060534 (360.5 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device interrupt 16 memory 0xd1100000-d1120000
...
[browndwarf@browndwarf02 ~]$ curl localhost:8080/api/heartbeat
I'm alived
...
Step 2. Container에서 Web Service 연결 Test
HW1에서 실행되는 NGINX docker container를 Reverse Proxy로 활용 가능한 지 타당성을 검사하기 위해 docker container 내부에서 curl command를 통해 위의 Web Service를 호출하였고, 정상적으로 동작하는 것을 확인하였다.
...
# Docker Container Shell로 접근
[root@browndwarf01 conf]# docker exec -it browndwarf-nginx bash
...
# curl 설치를 위해 Container의 Base Image OS 검사 --> Debian 계열
root@5b09e9433329:/# grep . /etc/*-release
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
...
# apt-get을 이용해서 net-tools, iputils-ping 설치
root@5b09e9433329:/# apt-get update
...
root@5b09e9433329:/# apt-get install net-tools
...
root@5b09e9433329:/# apt-get install iputils-ping
...
# Curl command로 HW2에서 동작하는 Web Service 호출
root@5b09e9433329:/# curl -H "Content-type: application/json" 16.8.35.227:8080/api/heartbeat
I'm alived
...
# Exit Docker Container Shell
root@5b09e9433329:/# exit
[root@browndwarf01 conf]#
Step 3. Container 내부 Resource File Back up 및 수정
/usr에 docker/nginx 디렉토리를 만든 후, 그 아래 Configuration File을 위한 conf folder와 Web Page Resourse를 위한 html 폴더를 만들었다. NGINX docker container에는 2개의 Configuration File - /etc/nginx/nginx.conf와 /etc/nginx/conf.d/default.conf - 이 있는데, 이 중에서 default.conf 파일을 변경해서 reverse proxy를 구성하는 과정이다.
...
# 현재 Path 확인
[root@browndwarf01 nginx]# pwd
/usr/docker/nginx
...
# 작업용 디렉토리 /html, /conf 생성
[root@browndwarf01 nginx]# mkdir html
[root@browndwarf01 nginx]# mkdir conf
[root@browndwarf01 nginx]# ls
conf html
...
# NGINX Container 내부에 들어가 기존의 default.conf 파일 backup 생성
[root@browndwarf01 conf]# docker exec -it 5b09e9433329 bash
root@5b09e9433329:/# cp /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak
root@5b09e9433329:/# exit
...
# NGINX Container 내부의 /etc/nginx/conf.d/default.conf 파일을 Host Machine으로 가져옴
[root@browndwarf01 nginx]# cd conf
[root@browndwarf01 conf]# docker cp browndwarf-nginx:/etc/nginx/conf.d/default.conf ./
# default.conf 수정
[root@browndwarf01 conf]# vi default.conf
...
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
# /api path에 대해 16.8.35.227:8080/api로 Routing 하게 설정
location /api {
proxy_pass http://16.8.35.227:8080/api;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
...
}
...
# 변경된 default.conf 파일을 NGINX Container의 /etc/nginx/conf.d 로 복사
[root@browndwarf01 conf]# docker cp ./default.conf browndwarf-nginx:/etc/nginx/conf.d/
Step 4. Restart NGINX Container
기존에 실행하던 NGINX container를 재시작한 후(이렇게 해야 Container 내부 변경 사항이 사라지지 않고 유지될 수 있다), HW1(hostname : browndwarf01)에서 GET localhost/api/heartbeat을 호출하면, HW2에서 동작하고 있는 Web Service로 연결되는 것을 확인할 수 있다.
[root@browndwarf01 conf]# docker restart browndwarf-nginx
browndwarf-nginx
[root@browndwarf01 conf]# curl http://localhost/api/heartbeat
I'm alived
Browser에서도 <pic 3>과 같이 동일한 결과를 확인할 수 있다.
여기서 사용한 docker command 문법은 아래와 같다.
docker cp [Host Machine Source File Path] [Container ID | Container Name]:[Container File Destination Path]
or
docker cp [Container ID | Container Name]:[Container File Source Path] [Host Machine File Destination Path]
docker restart [Container ID | Container Name]
- docker cp : host와 container간의 file을 복사. 양방향 모두 가능. Linux Shell의 cp 명령과 유사하나 Container 내부에 File Path를 가르킬 때에는 앞에 Container ID나 Container Name을 사용해야 한다.
- docker restart : Container restart.
4. Customized Docker Image 생성
위와 같이 번거로운 절차 없이 official NGINX docker image를 기반으로 Customizing한 NGINX Image를 만들 수도 있다. 작업하는 디렉토리에 Dockerfile이라는 이름의 파일을 생성하고, 아래와 같이 작성한다. (참조)
[root@browndwarf01 nginx]# vi Dockerfile
...
FROM nginx
COPY html /usr/share/nginx/html
COPY conf/default.conf /etc/nginx/conf.d/default.conf
VOLUME /usr/share/nginx/html
VOLUME /etc/nginx
위에서 만든 Dockerfile의 내용을 간단하게 설명하면...
- FROM nginx : Base Image은 NGINX image를 사용
- COPY html /usr/share/nginx/html : Host Machine에 있는 html 디렉토리를 NGINX docker image의 /usr/share/nginx/html 디렉토리로 복사
- COPY conf/default.conf /etc/nginx/conf.d/default.conf : Host Machine에 있는 conf/default.conf 파일을 NGINX docker image의 /usr/share/nginx/html 디렉토리로 복사
- VOLUMN /usr/share/nginx/html, /etc/nginx : NGINX에서 사용하는 두 개의 디렉토리를 Docker Volume으로 생성. (Option 사항)
그리고 Dockerfile을 만든 디렉토리에서 'docker build' command를 실행한다. 새로 만들어지는 Docker Image의 이름은 browndwarfnginx이며 별다른 Tag를 붙히지 않았기 때문에, default로 'latest'라는 Tag가 붙게 된다. Image 생성이 끝나고 'docker images' command를 실행하면 기존의 nginx Docker Image외에 browndwarfnginx Docker Image가 새로 만들어 진 것을 확인할 수 있다.
[root@browndwarf01 nginx]# docker build -t browndwarfnginx .
Sending build context to Docker daemon 12.8kB
Step 1/5 : FROM nginx
---> 5a3221f0137b
Step 2/5 : COPY html /usr/share/nginx/html
---> 755e8ff5fc53
Step 3/5 : COPY conf/default.conf /etc/nginx/conf.d/default.conf
---> 3f218b798f2b
Step 4/5 : VOLUME /usr/share/nginx/html
---> Running in d584991c441a
Removing intermediate container d584991c441a
---> 8c3e15f74ad4
Step 5/5 : VOLUME /etc/nginx
---> Running in 8e79859254e3
Removing intermediate container 8e79859254e3
---> 2ece12b09b4f
Successfully built 2ece12b09b4f
Successfully tagged browndwarfnginx:latest
# 만들어진 docker image 확인
[root@browndwarf01 nginx]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
browndwarfnginx latest 2ece12b09b4f 3 minutes ago 126MB
nginx latest 5a3221f0137b 2 months ago 126MB
새로 만들어진 Docker Image를 실행시키고, Container 상태를 확인하고 위에서 설정한 index.html과 Reverse Proxy 역할이 정상적으로 수행되는 지 아래와 같이 확인해 보았다.
# 새로 만들어진 browndwarfnginx Docker image 실행
[root@browndwarf01 nginx]# docker run --name browndwarf-nginx -p 80:80 -d browndwarfnginx
f37b28a51a7f4f32111bed953001036c2d508038908901d4e7ca6f0183d2a1a3
# 실행된 Container 확인
[root@browndwarf01 nginx]# docker container ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f37b28a51a7f browndwarfnginx "nginx -g 'daemon of…" 5 minutes ago Up 5 minutes 0.0.0.0:80->80/tcp browndwarf-nginx
# 변경된 index.html 을 Response로 확인
[root@browndwarf01 nginx]# curl localhost
<HTML>
<BODY>
<H2>Welcome. Browndwarf!!!</H2>
</BODY>
</HTML>
# Reverse Proxy 상태 확인
[browndwarf@browndwarf01 ~]$ curl localhost/api/heartbeat
I'm alived