Discourse가 jwilder/nginx proxy 및 acme-companion과 함께 작동

여러분 안녕하세요,

몇 시간 동안 이 문제로 막혀 있어서, 작동하는 해결책을 찾지 못해 도움을 요청합니다.

웹 서비스를 호스팅하는 여러 컨테이너가 있기 때문에, 트래픽을 올바른 컨테이너로 프록시하기 위해 자체 Nginx를 호스팅해야 합니다. (이것이 작동하지 않을 경우 Discourse를 별도 서버로 옮기는 것도 가능하지만, 이는 최후의 수단입니다.)

현재 다음 구성을 사용하고 있습니다:

이 구성이 어떻게 결합되는지 보여주는 좋은 다이어그램은 다음 페이지에서 찾을 수 있습니다:

다음 튜토리얼을 활용했는데 도움이 되었지만, 여전히 성공하지 못했습니다.

  1. How to Install Multiple Discourse Forums on the Same Server
  2. Use Nginx-Proxy and LetsEncrypt Companion to Host Multiple Websites - Singular Aspect

찾아본 이전 Discourse 스레드(2년 이상 된 것들)에는 다음이 포함됩니다:

제 discourse app.yml 파일에는 다음 내용이 포함되어 있습니다(도메인과 이메일은 비공개 처리됨):

docker_args:
  - "--net nginx-proxy"
  VIRTUAL_HOST: {domain}
  VIRTUAL_PORT: 80
  LETSENCRYPT_HOST: {domain}
  LETSENCRYPT_EMAIL: {email address}

아래 예시에서 노출된 포트는 주석 처리했습니다. 여러 변형을 시도했고, 다양한 제출물을 확인했습니다. 예를 들어, 이 게시물의 댓글: Discourse with jwilder/nginx-proxy howtos? , 또는 다른 곳에서 주석 처리되어 docker 변수로 VIRTUAL_PORT로 지정된 것을 보았습니다. 이 부분에 대해 약간 혼란스럽습니다.

#expose:
#  - "80"

모든 컨테이너는 동일한 네트워크, 예를 들어
docker network inspect nginx-proxy

[
    {
        "Name": "nginx-proxy",
        "Id": "d2715f513771f002711521838340b879bb9106ef50118fb29be6b0cf2d5f25e7",
        "Created": "2022-02-01T20:32:10.021632263Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "0b6296ccf7e31b67caf0d31ccc5485e30bd6f1746ceee7e6ce29617b892178cd": {
                "Name": "app",
                "EndpointID": "1cea3782ef9dea3cf0066add142121f569682c74ff8faa6b19813035eff8998c",
                "MacAddress": "02:72:f8:ee:03:32",
                "IPv4Address": "172.18.0.4/16",
                "IPv6Address": ""
            },
            "975ea34d56a65b722aa5e83b99f71039fd687b1aee4e8d4df3cb192b1d35c043": {
                "Name": "nginx-proxy",
                "EndpointID": "bc7a0b4602614be516ee0ceb1c4edf923b91bf7a6528c013157180a98d435807",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            },
            "e299a734584a765dbc10102a5bc9b0d0c98ac6096b6ae75dd1169105893e40af": {
                "Name": "letsencrypt-proxy",
                "EndpointID": "6b3588359ac4eec02618d3a7438d2750cb6e1c3d1cb6e1a8ce6378c568358a50",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

여기서 볼 수 있듯이 내 인증서는 성공적으로 생성되었지만, 현재 502 오류를 받고 있습니다.

discourse 컨테이너에서 nginx 로그를 확인하여 디버깅을 시도했지만, 로그 파일은 생성되지만 로그 내용이 채워지지 않습니다:

root@ubuntu-vm-dev-app:/var/log/nginx# ls -lt
total 692
-rw-r--r-- 1 www-data www-data      0 Feb  4 07:23 access.letsencrypt.log
-rw-r--r-- 1 www-data www-data      0 Feb  4 07:23 error.letsencrypt.log
-rw-r--r-- 1 www-data www-data      0 Feb  4 07:23 error.log
-rw-r--r-- 1 www-data www-data      0 Feb  1 23:04 access.log
-rw-r--r-- 1 www-data www-data 700917 Jan 31 23:48 error.log.1
-rw-r--r-- 1 www-data www-data      0 Jan 31 22:55 access.letsencrypt.log.1
-rw-r--r-- 1 www-data www-data      0 Jan 31 22:55 error.letsencrypt.log.1

제 docker 로그는 다음과 같으며, 지금까지 가장 유용한 정보인 것 같습니다(공개 IP와 도메인 이름은 제거했습니다.):

nginx-proxy          | nginx.1     | 2022/02/04 08:35:04 [error] 206#206: *93 connect() failed (111: Connection refused) while connecting to upstream, client: {REDACTED_PUBLIC_IP}, server: {MY_FORUM_DOMAIN}.com, request: "GET / HTTP/2.0", upstream: "http://172.18.0.4:80/", host: "{MY_FORUM_DOMAIN}.com"
nginx-proxy          | nginx.1     | {MY_FORUM_DOMAIN}.com {REDACTED_PUBLIC_IP} - - [04/Feb/2022:08:35:04 +0000] "GET / HTTP/2.0" 502 559 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36" "172.18.0.4:80"
nginx-proxy          | nginx.1     | 2022/02/04 08:35:06 [error] 206#206: *93 connect() failed (111: Connection refused) while connecting to upstream, client:{REDACTED_PUBLIC_IP}, server: {MY_FORUM_DOMAIN}.com, request: "GET /service-worker.js HTTP/2.0", upstream: "http://172.18.0.4:80/service-worker.js", host: "{MY_FORUM_DOMAIN}.com", referrer: "https://{MY_FORUM_DOMAIN}.com/service-worker.js"
nginx-proxy          | nginx.1     | {MY_FORUM_DOMAIN}.com {REDACTED_PUBLIC_IP} - - [04/Feb/2022:08:35:06 +0000] "GET /service-worker.js HTTP/2.0" 502 559 "https://{MY_FORUM_DOMAIN}.com/service-worker.js" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36" "172.18.0.4:80"
nginx-proxy          | nginx.1     | 2022/02/04 08:35:19 [error] 206#206: *93 connect() failed (111: Connection refused) while connecting to upstream, client:{REDACTED_PUBLIC_IP}, server: {MY_FORUM_DOMAIN}.com, request: "GET /service-worker.js HTTP/2.0", upstream: "http://172.18.0.4:80/service-worker.js", host: "{MY_FORUM_DOMAIN}.com", referrer: "https://{MY_FORUM_DOMAIN}.com/service-worker.js"
nginx-proxy          | nginx.1     | {MY_FORUM_DOMAIN}.com{REDACTED_PUBLIC_IP} - - [04/Feb/2022:08:35:19 +0000] "GET /service-worker.js HTTP/2.0" 502 559 "https://{MY_FORUM_DOMAIN}.com/service-worker.js" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36" "172.18.0.4:80"

컨테이너의 app.yml 파일에서 지정된 포트를 통해 discourse 컨테이너로의 통신이 정상임을 확인했습니다. 예를 들어, 브라우저에서 요청을 수행하기 전에 discourse 컨테이너에 로그인하여 포트 80에서 netcat을 실행했습니다.

root@ubuntu-vm-dev-app:/var/log/nginx# netcat -lnvp 80
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 172.18.0.2.
Ncat: Connection from 172.18.0.2:48776.
GET / HTTP/1.1
Host: {REDACTED_HOST}.com
Connection: close
X-Real-IP: {REDACTED_PUBLIC-IP}
X-Forwarded-For: {REDACTED_PUBLIC-IP}
X-Forwarded-Proto: https
X-Forwarded-Ssl: on
X-Forwarded-Port: 443
cache-control: max-age=0
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
sec-fetch-site: none
sec-fetch-mode: navigate
sec-fetch-user: ?1
sec-fetch-dest: document
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9
cookie: _t=azl2WkpyN1hnbENOd3pyZ1d6V3ZXUT....

root@ubuntu-vm-dev-app:/var/log/nginx# 

nginx-proxy 컨테이너에서 생성된 Nginx 구성의 예는 다음과 같습니다:

root@975ea34d56a6:/etc/nginx/conf.d# cat default.conf 
# nginx-proxy version : 0.10.0-11-g42c8b0c
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
# scheme used to connect to this server
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
  default $http_x_forwarded_proto;
  ''      $scheme;
}
# If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
# server port the client connected to
map $http_x_forwarded_port $proxy_x_forwarded_port {
  default $http_x_forwarded_port;
  ''      $server_port;
}
# If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any
# Connection header that may have been passed to this server
map $http_upgrade $proxy_connection {
  default upgrade;
  '' close;
}
# Apply fix for very long server names
server_names_hash_bucket_size 128;
# Default dhparam
ssl_dhparam /etc/nginx/dhparam/dhparam.pem;
# Set appropriate X-Forwarded-Ssl header based on $proxy_x_forwarded_proto
map $proxy_x_forwarded_proto $proxy_x_forwarded_ssl {
  default off;
  https on;
}
gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
log_format vhost '$host $remote_addr - $remote_user [$time_local] '
                 '"$request" $status $body_bytes_sent '
                 '"$http_referer" "$http_user_agent" '
                 '"$upstream_addr"';
access_log off;
		ssl_protocols TLSv1.2 TLSv1.3;
		ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
		ssl_prefer_server_ciphers off;
resolver 127.0.0.11;
# HTTP 1.1 support
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
# Mitigate httpoxy attack (see README for details)
proxy_set_header Proxy "";
server {
	server_name _; # This is just an invalid value which will never trigger on a real hostname.
	server_tokens off;
	listen 80;
	access_log /var/log/nginx/access.log vhost;
	return 503;
}
server {
	server_name _; # This is just an invalid value which will never trigger on a real hostname.
	server_tokens off;
	listen 443 ssl http2;
	access_log /var/log/nginx/access.log vhost;
	return 503;
	ssl_session_cache shared:SSL:50m;
	ssl_session_tickets off;
	ssl_certificate /etc/nginx/certs/default.crt;
	ssl_certificate_key /etc/nginx/certs/default.key;
}
# {REDACTED_DOMAIN}.com
upstream {REDACTED_DOMAIN}.com {
	## Can be connected with "nginx-proxy" network
	# app
	server 172.18.0.4:80;
}
server {
	server_name {REDACTED_DOMAIN}.com;
	listen 80 ;
	access_log /var/log/nginx/access.log vhost;
	# Do not HTTPS redirect Let'sEncrypt ACME challenge
	location ^~ /.well-known/acme-challenge/ {
		auth_basic off;
		auth_request off;
		allow all;
		root /usr/share/nginx/html;
		try_files $uri =404;
		break;
	}
	location / {
		return 301 https://$host$request_uri;
	}
}
server {
	server_name {REDACTED_DOMAIN}.com;
	listen 443 ssl http2 ;
	access_log /var/log/nginx/access.log vhost;
	ssl_session_timeout 5m;
	ssl_session_cache shared:SSL:50m;
	ssl_session_tickets off;
	ssl_certificate /etc/nginx/certs/{REDACTED_DOMAIN}.com.crt;
	ssl_certificate_key /etc/nginx/certs/{REDACTED_DOMAIN}.com.key;
	ssl_dhparam /etc/nginx/certs/{REDACTED_DOMAIN}.com.dhparam.pem;
	ssl_stapling on;
	ssl_stapling_verify on;
	ssl_trusted_certificate /etc/nginx/certs/{REDACTED_DOMAIN}.com.chain.pem;
	add_header Strict-Transport-Security "max-age=31536000" always;
	include /etc/nginx/vhost.d/default;
	location / {
		proxy_pass http://{REDACTED_DOMAIN}.com;
	}
}

누군가 이 구성을 작동시킨 경험이 있는지, 또는 제가 받는 502 오류를 디버깅하는 방법에 대한 아이디어가 있는지 궁금합니다. 오류가 여러 변수에 의해 발생할 수 있다는 것은 알지만, 제 자신과 유사한 설정을 구성할 때 이 문제를 겪어본 분의 해결책이 도움이 될 수 있을 것입니다.

감사합니다.

여러 프록시 서버가 포함된 디스커스 설치 환경에서 디버깅하는 방법에 대해 확인해 주세요:
서브폴더 디스커스 설치 디버깅 방법

프록시 설정은 정상적으로 작동하는 것 같지만, Discourse는 그렇지 않습니다. 제 추측으로는 데이터베이스에 접근할 수 없는 것 같습니다. 아래와 같은 경로에서 로그를 확인해 볼 수 있습니다:

/var/discourse/shared/standalone/logs/rails/production.log

"GET /server-worker.js HTTP/2.0" **502** […]
저에게는 정적 파일을 가져오는 과정에서 502 오류가 발생한 것처럼 보입니다. 프록시가 작동하지 않는 것 같습니다.

아, 그건 디스코urs의 nginx가 아니라 프록시 nginx에서 나온 거군요. 죄송합니다.

말씀하신 내용에서 눈에 띄게 잘못된 부분은 보이지 않지만, 작은 부분들이 많아서 그렇습니다. 아마도 discourse가 잘못된 docker 네트워크에 있는 것일 수 있습니다(하지만 그 부분은 이미 해결하려고 시도하신 것 같더군요).

지난 몇 달간 계속 말씀하신 것과 같은 작업을 어떻게 수행할지 알아볼 생각만 하고 있었는데, 다른 일들을 의뢰받느라 계속 시간을 쓰고 있습니다. 아마도 앞으로 한두 주 안에 가능할 것 같지만, 그 말을 몇 달째 하고 있는 셈이네요.

[…] "https://… […]

외부 프록시를 디스코urs Nginx의 http-url로 연결하도록 설정해 보십시오. https가 아닌 http로 연결해야 합니다.

@rrit @pfaffman 답변과 제안해 주셔서 감사합니다.

오늘 다시 한번 이 문제를 파고들었고, 꽤 시간이 걸렸지만 마침내 작동하게 되었습니다. 다른 분들에게 도움이 될 수 있기를 바라며 제 구성과 디버깅 방법을 공유합니다.

참고: nginx/acme-companion/discourse 컨테이너가 어떻게 '연결’되어 인증서/리버스 프록시를 자동화하는지에 대한 정보는 제 첫 번째 게시글을 참고해 주세요.

디버깅 방법에 관심이 없다면 아래 제 솔루션이 게시된 부분(docker-compose 파일과 app.yml 파일)으로 건너뛰세요

제 구성에서 식별된 주요 오류

제가 겪었던 오류 중 하나는 여러 튜토리얼을 맹목적으로 따라 하다 보니 혼란이 생겼기 때문이었습니다. 첫 번째 게시글에서 보실 수 있듯이, 저는 "netcat -lnvp 80"을 사용하여 discourse 앱 컨테이너의 포트 80에서 리스닝할 수 있었습니다. 그런데 리버스 프록시가 discourse 컨테이너의 리스닝 포트로 트래픽을 보내길 원한다면, 해당 포트는 netcat으로 리스닝할 수 없어야 합니다… 즉, ‘사용 중(in use)’ 상태여야 합니다.

문제는 제가 다음 템플릿을 사용했던 것이었습니다: web.socketed.template.yml
이것은 discourse 컨테이너의 nginx가 TCP/IP 포트에서 리스닝하는 대신 Unix 소켓을 사용한다는 의미입니다. 템플릿의 예시와 nginx에 적용되는 구성은 다음과 같습니다:

ubuntu@ubuntu-vm-dev:/var/discourse/templates$ cat web.socketed.template.yml 
run:
  - file:
     path: /etc/runit/1.d/remove-old-socket
     chmod: "+x"
     contents: |
        #!/bin/bash
        rm -f /shared/nginx.http*.sock
  - file:
     path: /etc/runit/3.d/remove-old-socket
     chmod: "+x"
     contents: |
        #!/bin/bash
        rm -rf /shared/nginx.http*.sock
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock;
       set_real_ip_from unix:;
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 443 ssl http2;/
     to: |
       listen unix:/shared/nginx.https.sock ssl http2;
       set_real_ip_from unix:;

문제는 제 nginx-proxy 컨테이너의 nginx가 discourse 컨테이너로 트래픽을 성공적으로 전달했지만, 공유 Unix 소켓을 사용하는 대신 포트 80으로 전달하고 있었다는 것입니다.

nginx-proxy 문서에서 **가상 포트(Virtual Port)**가 지정되지 않는 한 기본 포트는 포트 80임을 확인할 수 있습니다.
스크린샷 첨부: GitHub - nginx-proxy/nginx-proxy: Automated Nginx Reverse Proxy for Docker · GitHub

nginx-proxy 컨테이너를 설정하는 데 시간을 낭비하기보다는, 저는 web.socketed.template.yml을 제거했습니다. 그 후, 더 이상 필요하지 않았기 때문에 discourse 컨테이너가 노출하고 있던 모든 포트를 제거했습니다. discourse 컨테이너가 올바른 docker 네트워크(nginx-proxy와 acme-companion와 동일한 네트워크)에 있으면 정상적으로 작동해야 합니다.

디버깅 팁

1. 네트워크 디버깅

1.1 Docker 네트워크
네트워크가 성공적인지 테스트하는 방법의 예는 다음과 같은 명령어입니다 (docker 앱과 nginx/acme 컨테이너를 시작 한 후).

ubuntu@ubuntu-vm-dev:/var/discourse$ docker network inspect nginx-proxy
 {
        "Name": "nginx-proxy",
        "Id": "d2715f513771f002711521838340b879bb9106ef50118fb29be6b0cf2d5f25e7",
        "Created": "2022-02-01T20:32:10.021632263Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "205772214b77a6bf49a76d082eef216aa5fc4ca15a39ae2c8fefc0b3eeb61e00": {
                "Name": "nginx-test_webserver2_1",
                "EndpointID": "ee3096663b1a90443906652ff3a192243fa1f60081e5bbee190a23f1d23393f7",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            },
            "3050831bf600ae02c18e5045549267b583ec732d6f1550f869ab651dfc7d8dca": {
                "Name": "nginx-test_nginx_1",
                "EndpointID": "a7f95f2a7589ca3823da01b9d28244bbd81815d6f5cc1f2f89804a488ef2148d",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            "696ce8d129a62ce37ae8373ed4f603fd5d731f5f29166bd6fa889631ef0ae606": {
                "Name": "nginx-proxy",
                "EndpointID": "99d90539668b97652ffca7e3301aa83c40fe7b31ca8eea2a8a4d9bf7afe19ac0",
                "MacAddress": "02:42:ac:12:00:04",
                "IPv4Address": "172.18.0.4/16",
                "IPv6Address": ""
            },
            "d3faf6489ca6617dceda4f2907ee6c055a1d81e3590c3eab2768601dfc0b60d7": {
                "Name": "app",
                "EndpointID": "aed6bfcc61e439e2206615a1569355c529a63a21c356e54da378f630d44fd3bc",
                "MacAddress": "02:72:f8:ee:03:32",
                "IPv4Address": "172.18.0.6/16",
                "IPv6Address": ""
            },
            "e04f2125001d5f346255312f10cdd0ab176388cddb0819b0022c5988a23303eb": {
                "Name": "letsencrypt-proxy",
                "EndpointID": "0088bcd942aed41c9351b935ed7b115e49046cc3a8308ca5a8d3828a194ecfee",
                "MacAddress": "02:42:ac:12:00:05",
                "IPv4Address": "172.18.0.5/16",
                "IPv6Address": ""
            }
        },

참고: 네트워크 이름을 사용 중인 이름으로 변경하세요 (nginx-proxy라는 이름을 사용 중이 아니라면)

이후, nginx-proxy 컨테이너 구성에서 docker 네트워크의 이러한 IP 주소를 볼 수 있어야 합니다. 이는 다음을 통해 접근하고 확인할 수 있습니다:

ubuntu@ubuntu-vm-dev:/var/discourse$ docker exec -it nginx-proxy /bin/bash
root@696ce8d129a6:/app# cd /etc/nginx/conf.d/
root@696ce8d129a6:/etc/nginx/conf.d# cat default.conf
... NGINX Configuration will be here ... 

성공적인 네트워크 통신을 테스트하려면, upstream 위치 중 하나에 지정된 IP 주소와 상호작용해 보세요. 예를 들어, nginx-proxy 컨테이너에서 할당된 docker 네트워크 IP 주소를 통해 discourse 컨테이너와 상호작용을 시도해 보세요.

예를 들어, 제 구성 파일에는 다음이 포함되어 있습니다:

upstream my_forum_domain.com {
	## Can be connected with "nginx-proxy" network
	# app
	server 172.18.0.6:80;
}
```\n제 discourse 컨테이너의 upstream IP가 **172.18.0.6**이므로, **discourse 컨테이너**에 대해 curl을 수행할 때 성공적인 출력을 볼 수 있어야 합니다.

root@696ce8d129a6:/etc/nginx/conf.d# curl 172.18.0.6
… Curl request output …

**1.2. 네트워크 문제 해결 도구 (Netstat, lsof, netcat)**
다음 도구들은 'netstat', 'lsof', 'netcat'을 사용하여 문제를 해결하는 데 도움이 됩니다. discourse 컨테이너에는 이러한 도구가 미리 설치되어 있지 않으므로 다음을 통해 설치하세요:

root@ubuntu-vm-dev-app:/# apt-get install lsof
root@ubuntu-vm-dev-app:/# apt-get install net-tools
root@ubuntu-vm-dev-app:/# apt-get install netcat

**lsof**
lsof를 통해 nginx 프로세스가 포트 80에서 리스닝하는 것을 볼 수 있어야 합니다.

root@ubuntu-vm-dev-app:/# lsof -i :80
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 55 root 6u IPv4 254317 0t0 TCP *:http (LISTEN)
root@ubuntu-vm-dev-app:/#

**netstat**
다음 netstat 명령어도 올바른 프로세스/포트가 사용되고 있는지 확인하는 데 유용합니다. 예를 들어, HTTP(80), redis(6379), PostgreSQL(5432), Unicorn(3000) 등과 관련된 포트를 볼 수 있습니다.

root@ubuntu-vm-dev-app:/# netstat -peanut
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State User Inode PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 0 254317 55/nginx: master pr
tcp 0 0 127.0.0.1:3000 0.0.0.0:* LISTEN 1000 255560 -
tcp 0 0 0.0.0.0:5432 0.0.0.0:* LISTEN 105 255137 -
tcp 0 0 127.0.0.11:46697 0.0.0.0:* LISTEN 0 254194 -
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN 106 255126 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37600 ESTABLISHED 106 255583 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37608 ESTABLISHED 106 255610 -
tcp 0 0 127.0.0.1:37632 127.0.0.1:6379 ESTABLISHED 1000 254766 -
tcp 0 0 127.0.0.1:37626 127.0.0.1:6379 ESTABLISHED 1000 254745 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37634 ESTABLISHED 106 254773 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37650 ESTABLISHED 106 255950 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37668 ESTABLISHED 106 255965 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37612 ESTABLISHED 106 255618 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37602 ESTABLISHED 106 255585 -
tcp 0 0 127.0.0.1:37624 127.0.0.1:6379 ESTABLISHED 1000 254739 -
tcp 0 0 127.0.0.1:37594 127.0.0.1:6379 ESTABLISHED 1000 255578 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37594 ESTABLISHED 106 254527 -
tcp 0 0 127.0.0.1:37618 127.0.0.1:6379 ESTABLISHED 1000 255635 -
tcp 0 0 127.0.0.1:37586 127.0.0.1:6379 ESTABLISHED 1000 255168 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37622 ESTABLISHED 106 255645 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37624 ESTABLISHED 106 254740 -
tcp 0 0 127.0.0.1:37600 127.0.0.1:6379 ESTABLISHED 1000 254537 -
tcp 0 0 127.0.0.1:37610 127.0.0.1:6379 ESTABLISHED 1000 254563 -
tcp 0 0 127.0.0.1:3000 127.0.0.1:36678 TIME_WAIT 0 0 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37620 ESTABLISHED 106 255642 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37636 ESTABLISHED 106 254779 -
tcp 0 0 127.0.0.1:37628 127.0.0.1:6379 ESTABLISHED 1000 254751 -
tcp 0 0 127.0.0.1:37590 127.0.0.1:6379 ESTABLISHED 1000 254361 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37626 ESTABLISHED 106 254746 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37586 ESTABLISHED 106 255169 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37590 ESTABLISHED 106 255173 -
tcp 0 0 127.0.0.1:37668 127.0.0.1:6379 ESTABLISHED 1000 254849 -
tcp 0 0 127.0.0.1:37622 127.0.0.1:6379 ESTABLISHED 1000 254582 -
tcp 0 0 127.0.0.1:37634 127.0.0.1:6379 ESTABLISHED 1000 254772 -
tcp 0 0 127.0.0.1:37602 127.0.0.1:6379 ESTABLISHED 1000 254541 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37630 ESTABLISHED 106 254760 -
tcp 0 0 127.0.0.1:37612 127.0.0.1:6379 ESTABLISHED 1000 255617 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37628 ESTABLISHED 106 254752 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37610 ESTABLISHED 106 255612 -
tcp 0 0 127.0.0.1:3000 127.0.0.1:36682 TIME_WAIT 0 0 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37632 ESTABLISHED 106 254767 -
tcp 0 0 127.0.0.1:37650 127.0.0.1:6379 ESTABLISHED 1000 254826 -
tcp 0 0 127.0.0.1:37608 127.0.0.1:6379 ESTABLISHED 1000 254559 -
tcp 0 0 127.0.0.1:37620 127.0.0.1:6379 ESTABLISHED 1000 255641 -
tcp 0 0 127.0.0.1:37636 127.0.0.1:6379 ESTABLISHED 1000 254778 -
tcp 0 0 127.0.0.1:37630 127.0.0.1:6379 ESTABLISHED 1000 254759 -
tcp 0 0 127.0.0.1:6379 127.0.0.1:37618 ESTABLISHED 106 255636 -
tcp6 0 0 :::5432 :::* LISTEN 105 255138 -
tcp6 0 0 :::6379 :::* LISTEN 106 255127 -
udp 0 0 127.0.0.1:54569 127.0.0.1:54569 ESTABLISHED 105 255145 -
udp 0 0 127.0.0.11:44019 0.0.0.0:* 0 254193 -
root@ubuntu-vm-dev-app:/#

**nc**
nc를 사용하여 리스닝 포트가 올바르게 구성/리스닝되고 있는지 확인합니다. (물론 netstat에서도 이미 표시되어야 합니다).

root@ubuntu-vm-dev-app:/# nc -zv localhost 1-10000 2>&1 | grep -i succeeded
Connection to localhost (127.0.0.1) 80 port [tcp/http] succeeded!
Connection to localhost (127.0.0.1) 3000 port [tcp/*] succeeded!
Connection to localhost (127.0.0.1) 5432 port [tcp/postgresql] succeeded!
Connection to localhost (127.0.0.1) 6379 port [tcp/redis] succeeded!

**1.3 프로세스**
또 다른 유용한 명령어: ps aux

root@ubuntu-vm-dev-app:/# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 6772 3252 pts/0 Ss+ 15:20 0:00 /bin/bash /sbin/boot
root 41 0.0 0.0 2340 636 pts/0 S+ 15:20 0:00 /usr/bin/runsvdir -P /etc/service
root 42 0.0 0.0 2188 696 ? Ss 15:20 0:00 runsv cron
root 43 0.0 0.0 2188 636 ? Ss 15:20 0:00 runsv rsyslog
root 44 0.0 0.0 2188 632 ? Ss 15:20 0:00 runsv nginx
root 45 0.0 0.0 2188 636 ? Ss 15:20 0:00 runsv postgres
root 46 0.0 0.0 2188 636 ? Ss 15:20 0:00 runsv redis
root 47 0.0 0.0 2188 636 ? Ss 15:20 0:00 runsv unicorn
root 48 0.0 0.0 151068 3868 ? Sl 15:20 0:00 rsyslogd -n
root 49 0.0 0.0 2336 700 ? S 15:20 0:00 svlogd /var/log/redis
root 50 0.0 0.0 6620 2768 ? S 15:20 0:00 cron -f
redis 51 0.1 0.0 54180 5828 ? Sl 15:20 0:02 /usr/bin/redis-server *:6379
root 52 0.0 0.0 2336 636 ? S 15:20 0:00 svlogd /var/log/postgres
postgres 53 0.0 0.3 213196 28676 ? S 15:20 0:00 /usr/lib/postgresql/13/bin/postmaster -D /etc/postgresql/13/main
discour+ 54 0.0 0.0 15256 3924 ? S 15:20 0:00 /bin/bash config/unicorn_launcher -E production -c config/unicorn.conf.rb
root 55 0.0 0.0 21280 7128 ? S 15:20 0:00 nginx: master process /usr/sbin/nginx
www-data 69 0.0 0.0 22156 5004 ? S 15:20 0:00 nginx: worker process
www-data 70 0.0 0.0 22124 4524 ? S 15:20 0:00 nginx: worker process
www-data 71 0.0 0.0 21656 3768 ? S 15:20 0:00 nginx: cache manager process
postgres 75 0.0 0.0 213296 6344 ? Ss 15:20 0:00 postgres: 13/main: checkpointer
postgres 76 0.0 0.0 213196 5896 ? Ss 15:20 0:00 postgres: 13/main: background writer
postgres 77 0.0 0.1 213196 10028 ? Ss 15:20 0:00 postgres: 13/main: walwriter
postgres 78 0.0 0.1 213736 8492 ? Ss 15:20 0:00 postgres: 13/main: autovacuum launcher
postgres 79 0.0 0.0 67960 5548 ? Ss 15:20 0:00 postgres: 13/main: stats collector
postgres 80 0.0 0.0 213752 6868 ? Ss 15:20 0:00 postgres: 13/main: logical replication launcher
discour+ 81 0.4 3.1 436276 253572 ? Sl 15:20 0:08 unicorn master -E production -c config/unicorn.conf.rb
postgres 99 0.0 0.3 220252 30612 ? Ss 15:20 0:00 postgres: 13/main: discourse discourse [local] idle
discour+ 116 0.6 3.7 831804 306708 ? SNl 15:20 0:12 sidekiq 6.4.1 discourse [0 of 5 busy]
discour+ 125 0.4 3.6 750356 297108 ? Sl 15:20 0:07 unicorn worker[0] -E production -c config/unicorn.conf.rb
discour+ 133 0.3 3.5 737812 289896 ? Sl 15:20 0:07 unicorn worker[1] -E production -c config/unicorn.conf.rb
postgres 147 0.0 0.3 219372 27792 ? Ss 15:20 0:00 postgres: 13/main: discourse discourse [local] idle
root 1858 0.0 0.0 7036 3828 pts/1 Ss 15:46 0:00 /bin/bash
postgres 1982 0.0 0.2 217012 24008 ? Ss 15:47 0:00 postgres: 13/main: discourse discourse [local] idle
discour+ 2236 0.0 0.0 13760 2216 ? S 15:51 0:00 sleep 1
root 2237 0.0 0.0 9636 3292 pts/1 R+ 15:51 0:00 ps aux
root@ubuntu-vm-dev-app:/#

**참고:** 위의 출력을 제공한 이유는 이상하거나 제 배포 환경과 다른 점을 찾아내는 데 도움이 되기 위해서입니다. 대신, 리버스 프록시 없이 자체적으로 작동하는 배포 환경을 구축하고 다음과 같은 것들을 비교해 볼 수 있습니다:
* 네트워크
* 프로세스
* 파일 생성/수정
* 로그 출력

## 제 작동하는 구성 파일

**1. ./launcher rebuild app 시 로그 출력 예시**
  • /usr/bin/docker run --shm-size=512m -d --restart=always -e LANG=en_US.UTF-8 -e RAILS_ENV=production -e UNICORN_WORKERS=2 -e UNICORN_SIDEKIQS=1 -e RUBY_GLOBAL_METHOD_CACHE_SIZE=131072 -e RUBY_GC_HEAP_GROWTH_MAX_SLOTS=40000 -e RUBY_GC_HEAP_INIT_SLOTS=400000 -e RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR=1.5 -e DISCOURSE_DB_SOCKET=/var/run/postgresql -e DISCOURSE_DB_HOST= -e DISCOURSE_DB_PORT= -e VIRTUAL_HOST={your_domain}.com -e LETSENCRYPT_HOST={your_domain}.com -e LETSENCRYPT_EMAIL=steve@{your_email_domain}.com -e LC_ALL=en_US.UTF-8 -e LANGUAGE=en_US.UTF-8 -e EMBER_CLI_PROD_ASSETS=1 -e DISCOURSE_HOSTNAME={your_domain}.com -e DISCOURSE_DEVELOPER_EMAILS=steve@{your_email_domain}.com -e DISCOURSE_SMTP_ADDRESS=smtp.mailgun.org -e DISCOURSE_SMTP_PORT=587 -e DISCOURSE_SMTP_USER_NAME=noreply@mail.{your_domain}.com -e DISCOURSE_SMTP_PASSWORD={your_password} -e DISCOURSE_SMTP_DOMAIN=mail.{your_domain}.com -e DISCOURSE_NOTIFICATION_EMAIL=noreply@mail.{your_domain}.com -h ubuntu-vm-dev-app -e DOCKER_HOST_IP=172.17.0.1 --name app -t -v /var/discourse/shared/standalone:/shared -v /var/discourse/shared/standalone/log/var-log:/var/log --mac-address 02:72:f8:ee:03:32 --network nginx-proxy local_discourse/app /sbin/boot
    d3faf6489ca6617dceda4f2907ee6c055a1d81e3590c3eab2768601dfc0b60d7
    ubuntu@ubuntu-vm-dev:/var/discourse$


**2. 제 docker-compose 파일**

ubuntu@ubuntu-vm-dev:~/nginx-test$ cat docker-compose.yml
version: ‘3’
services:
nginx-proxy:
image: jwilder/nginx-proxy
container_name: nginx-proxy
ports:
- “80:80”
- “443:443”
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- letsencrypt-certs:/etc/nginx/certs
- letsencrypt-vhost-d:/etc/nginx/vhost.d
- letsencrypt-html:/usr/share/nginx/html
letsencrypt-proxy:
image: jrcs/letsencrypt-nginx-proxy-companion
container_name: letsencrypt-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt-certs:/etc/nginx/certs
- letsencrypt-vhost-d:/etc/nginx/vhost.d
- letsencrypt-html:/usr/share/nginx/html
environment:
- DEFAULT_EMAIL=steve@{your_email_domain}.com
- NGINX_PROXY_CONTAINER=nginx-proxy

networks:
default:
external:
name: nginx-proxy

volumes:
letsencrypt-certs:
letsencrypt-vhost-d:
letsencrypt-html:

**3. 제 discourse app.yml 파일**

ubuntu@ubuntu-vm-dev:/var/discourse$ cat containers/app.yml
templates:

  • “templates/postgres.template.yml”
  • “templates/redis.template.yml”
  • “templates/sshd.template.yml”
  • “templates/web.template.yml”

docker_args:

  • “–network nginx-proxy”

params:
db_default_text_search_config: “pg_catalog.english”

db_shared_buffers: “128MB”

env:
VIRTUAL_HOST: {your_domain}.com
LETSENCRYPT_HOST: example.com
LETSENCRYPT_EMAIL: steve@{your_email_domain}.com
LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8
LANGUAGE: en_US.UTF-8
EMBER_CLI_PROD_ASSETS: 1

DISCOURSE_DEFAULT_LOCALE: en

UNICORN_WORKERS: 2

DISCOURSE_HOSTNAME: example.com

DISCOURSE_DEVELOPER_EMAILS: ‘steve@{email_domain}.com’

DISCOURSE_SMTP_ADDRESS: smtp.mailgun.org
DISCOURSE_SMTP_PORT: 587
DISCOURSE_SMTP_USER_NAME: noreply@mail.{domain.com}.com
DISCOURSE_SMTP_PASSWORD: “{password}”
#DISCOURSE_SMTP_ENABLE_START_TLS: true # (optional, default true)
DISCOURSE_SMTP_DOMAIN: mail.{domain}.com
DISCOURSE_NOTIFICATION_EMAIL: noreply@mail.{domain}.com

volumes:

  • volume:
    host: /var/discourse/shared/standalone
    guest: /shared
  • volume:
    host: /var/discourse/shared/standalone/log/var-log
    guest: /var/log

hooks:
after_code:
- exec:
cd: $home/plugins
cmd:
- git clone GitHub - discourse/docker_manager: Plugin for use with discourse docker image · GitHub
- git clone GitHub - discourse/discourse-spoiler-alert: A plugin for discourse to hide spoilers behind the spoiler-alert jQuery plugin · GitHub
- git clone GitHub - discourse/discourse-solved: Allow accepted answers on topics · GitHub
- git clone GitHub - discourse/discourse-cakeday: Show a birthday cake emoji beside the names of members on their join anniversary, or their actual birthday -- and a browsable directory of upcoming anniversaries / birthdays. · GitHub

run:

  • exec: echo “Beginning of custom commands”
  • exec: echo “End of custom commands”
위 내용이 이 문제에 막혀 있는 다른 분들에게 도움이 되길 바랍니다 :)