중국 내에서 업그레이드 실패: git 문제 때문

저희는 Aliyun/Alibaba의 Ubuntu 20.04 서버에서 Discourse 인스턴스를 운영하고 있으며, Git 관련 모든 작업에서 **만리장성(GFW)**으로 인해 문제가 발생하고 있습니다. launcher rebuild app 명령으로 수동 업그레이드를 시도하면 대부분 GnuTLS 오류(다양한 유형)로 인해 실패합니다. 이는 서버에 설치된 Git 버전과 무관하며, 실제로는 GFW 내부의 핸드셰이크 처리와 관련이 있습니다. 물론 세부 사항을 정확히 이해하지는 못하지만, 여러 출처에서 이 문제를 상세히 다루고 있습니다. 따라서 OpenSSL을 사용하여 Git을 수동으로 컴파일하는 것도 옵션이 될 수 없습니다.

때로는 pull 프로세스가 코어를 넘어 Docker Manager 플러그인까지 클론하는 데 성공하기도 하지만, 2~3개의 플러그인 pull 이후에는 보통 타임아웃이나 다른 오류가 발생합니다.

예시:

$ ./launcher rebuild app
Ensuring launcher is up to date
Fetching origin
Launcher is up-to-date
Stopping old container
+ /usr/bin/docker stop -t 60 app
app
cd /pups && git pull && git checkout v1.0.3 && /pups/bin/pups --stdin
fatal: unable to access 'https://github.com/discourse/pups.git/': gnutls_handshake() failed: The TLS connection was non-properly terminated.
76630913bae18d6b45b6b3ecc3ec390c1e69222a493f2ecf424ee06adf9d1002
** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one.
./discourse-doctor may help diagnose the problem.

아래와 같은 오류도 비교적 흔합니다:

fatal: unable to access 'https://github.com/discourse/discourse.git/': GnuTLS recv error (-54): Error in the pull function.

잠재적 해결책 1
GitHub에서 클론할 때 HTTPS 대신 SSH를 사용하면 결과가 더 좋거나 실패하지 않는 경우가 많습니다. 하지만 Discourse의 고유한 rebuild 작업 특성상 launcher가 HTTPS 대신 SSH를 통해 pull하도록 설정해야 할 위치가 어디인지 전혀 알지 못합니다. Discourse 인스턴스를 그렇게 설정할 수 있을까요?

잠재적 해결책 2
다른 옵션으로, GFW를 우회하여 포르노를 시청하는 용도로 중국 내부에서 차단된 리소스에 접근하기 위해 사용할 수 있는 SOCKS5 프록시를 보유하고 있습니다. Git이 socks:// 프로토콜을 사용하도록 구성할 수 있다는 점은 알고 있지만, 불행히도 Discourse에서 이 구성을 어떻게, 어디에 설정해야 하는지 이해하지 못해 Discourse launcher의 pull 프로세스가 프록시를 사용하도록 하는 방법을 모릅니다. 루트 사용자를 대상으로 git config --global을 사용하여 설정하는 것은 피하고, 대신 Discourse 저장소용 구성 파일에 이 정보를 포함하고 싶습니다. 이를 달성하는 방법을 알려주실 수 있는 분이 있을까요?

이는 매우 번거로운 문제입니다. 저희는 이 Discourse 인스턴스를 사내망(intranet)에서 사용하고 있으며, 현재 인스턴스가 한 달 이상 사실상 무용지물 상태라 운영에 심각한 영향을 미치고 있습니다.

app.yml의 env 섹션에 프록시 환경 변수를 전달하면 작동할까요?
그리고 GFW 환경에서 Rubygem을 위한 해결책은 다음과 같습니다:

Replace rubygems.org with taobao mirror to resolve network error in China 을 보셨나요?

정말 감사합니다. Ruby gems는 문제가 되지 않습니다. 처음부터 게시글에서 언급된 템플릿을 app.yml에 포함시켰기 때문에 완벽하게 작동하고 있습니다.

문제는 메인 저장소와 플러그인 저장소를 클론하는 것입니다.

불행히도 저는 Docker, 특히 docker-compose 파일에 서툰 편이라 Git 플래그에 대한 환경 변수를 확인해야 합니다. 추천할 만한 자료를 알고 계신가요?

Discourse는 제가 아는 방식대로 docker-compose를 사용하지 않았습니다.

web.china.template.yml에서처럼 아래 명령어를 before_web 훅에 추가하면 작동할 것이라고 생각합니다.

git config --global http.proxy socks5://yourproxy:port

빌드 후 더 이상 프록시가 필요하지 않다면, 아래 명령어를 after_web 훅에 추가하세요:

git config --global --unset http.proxy

모든 훅은 컨테이너 내부에서 실행되므로 문제가 되지 않을 것이라고 생각합니다.

Docker에 대해 제가 얼마나 무지한지를 보여주는 또 다른 증거입니다. 네, 분명히 docker-compose 파일이 아니죠. 이걸 "Docker file"이라고 부르는 건가요? 아니면 그 용어가 config.json을 가리키는 건가요? 어쨌든, 당신의 조언이 올바른 방향으로 저를 이끌어주었습니다. 다만 훅 이름은 before_web이 아니라 before_code여야 합니다.

간단히 말하면: shadowsocks-libev로 socks5를 설정하고, 로컬 머신에서 172.17.0.1(localhost가 아님)에서 리스닝하며, 당신의 메시지에 있는 대로 프록시 정보를 전달하고, 앱을 다시 빌드(rebuild)합니다.

여기에서 자세한 가이드를 작성할 예정입니다. 이 고통스러운 경험을 겪고 있는 사람들이 더 있을 거라고 생각하기 때문입니다. 현재 저는 테마 컴포넌트 저장소와 관련하여 여전히 문제를 겪고 있어 rebuild가 아직 성공하지 못했지만, 적어도 모든 플러그인 가져오기(fetches)는 통과했습니다.

주제와는 약간 관련이 적지만, 제가 겪고 있는 고통은 앱을 시작할 수 없다는 점입니다. 다른 머신에 있는 기존 redis-server 설정이 현재 앱 상태의 현실과 일치하지 않기 때문입니다. 그래서 컨테이너를 시작할 수 없고, 클로닝 시 타임아웃이 발생하는 테마 컴포넌트들을 GUI를 통해 비활성화할 수도 없습니다.

설명을 알려주셔서 정말 감사합니다. 다만 예시가 완벽하게 맞지 않아 몇 가지 코멘트를 추가하고 싶습니다.

  1. 이 부분은 이해가 되지 않습니다, 죄송합니다? env 명령어는 많은 정보를 제공하지만, 제 gitconfig와 관련된 정보는 없습니다.
  2. 1)을 이해하지 못했기 때문에 어떤 변수를 전달해야 하는지 파악할 수 없었습니다. 또한 app.yml의 env 섹션에 git flags를 추가하지 않고, hook를 통해 호출했습니다.
  3. 이는 필요하지 않았습니다. 컨테이너 전체가 socks를 통해 통신하는 것이 아니라 git fetch 프로세스만 socks를 통해 통신하도록 하고 싶었기 때문입니다. 다만 이 지점은 당신이 언급한 스레드의 원래 사용 사례에 더 특화되어 있었던 것 같습니다.

다시 한번 감사합니다. 당신의 조언 덕분에 올바른 방향으로 나아갈 수 있었습니다. Discourse 팀에게 엄지척입니다! :ok_hand:

Aliyun을 구매하고 중국 본토 내 지역을 선택하셨나요?

HK/국제 Aliyun에서는 이 문제가 발생하지 않습니다.

또한, 이 세부 사항에 대해 이미 논의하셨을 수도 있지만, 해당 내용을 찾지 못하셨을 경우를 대비해 openssl을 통해 git을 설치할 수 있는 스크립트를 공유합니다.

중국 내에서 고통 없는 수동 업그레이드

단계

  1. 중국 밖에서 SOCKS5 프록시 생성
  2. 중국 서버에서 프록시 연결 설정 및 구성
  3. 편집을 더 쉽게 하기 위한 템플릿 생성
  4. 템플릿에 git 프록시 설정 추가
  5. app.yml에 템플릿 포함
  6. 앱 재빌드

1 - 원격 SOCKS5

사용 편의성(그리고 친절한 가격 정책)을 위해 싱가포르와 같은 지역에 Digital Ocean 서버를 설정하는 것을 추천합니다. 표준 Ubuntu 서버를 사용하고, 기본적인 보안 요구 사항 구성(SSH 키 페어, UFW 등)을 거친 후 Shadowsocks를 설치하세요:

원격 머신에서
$ sudo apt install shadowsocks-libev

프록시 설정을 구성하세요:

$ cd /etc/shadowsocks-libev

# 원래 파일들을 유지하는 것을 좋아합니다
$ sudo cp config.json orig.config.json
$ sudo nano config.json

timeout과 method 설정에 주의하세요:

{
    "server":"123.123.123.123", # 원격 서버 IP
    "server_port":8400, # 사용자가 원하는 포트
    "local_port":1080,
    "password":"Swordfish", 
    "timeout":600, # <= 필수!
    "method":"chacha20-ietf-poly1305"
}

systemd 구성(/lib/systemd/system/shadowsocks-libev-local@.service)의 모든 설정을 두 번 확인하세요. shadowsocks-libev-local@.service를 활성화하고, 재부팅한 후 실행 중인 서비스를 확인하세요.

2 - 중국 서버에서 프록시 연결 설정

Discourse 머신에서

$ sudo apt install shadowsocks-libev

Aliyun을 사용 중이라면, 그들의 특이한 콘솔에서 방화벽 설정을 찾아 해당 포트 설정을 확인하세요.

클라이언트 머신에서 systemd 설정을 가지고 헤매지 않아도 되지만, Docker와 일반 사용을 위한 별도의 구성 파일을 유지하세요. Docker 컨텍스트 밖에서 SOCKS5 프록시를 사용하려는 경우 Docker에서 접근 가능한 네트워크 주소 대신 127.0.0.1을 사용하게 되므로, 이렇게 하는 것이 좋습니다.

$ cd /etc/shadowsocks-libev
$ sudo cp config.json local.json
$ sudo cp config.json docker.json

다음과 유사한 내용으로 구성을 수정하세요

$ sudo nano local.json

{
    "server":["123.123.123.123"], # 원격 머신의 IP
    "mode":"tcp_and_udp", # 제 설정에서 shadowsocks-libev 버전이 다르기 때문에 이 주석이 다릅니다
    "server_port":8400,
    "local_address":"127.0.0.1",
    "local_port":1080,
    "password":"Swordfish",
    "timeout":600, # <= 이 설정을 꼭 확인하세요
    "method":"chacha20-ietf-poly1305"
}

편의상 .bashrc에 별칭을 추가해 봅시다:

$ nano ~/.bashrc

# 붙여넣기
alias dockershadow='ss-local -c /etc/shadowsocks-libev/local.json'

Docker가 호스트 머신의 네트워크를 통해 통신하도록 다른 구성을 수정하세요

$ sudo nano docker.json

{
    "server":["123.123.123.123"],
    "mode":"tcp_and_udp",
    "server_port":8400,
    "local_address":"172.17.0.1",
    "local_port":1080,
    "password":"Swordfish",
    "timeout":600,
    "method":"chacha20-ietf-poly1305"
}

Docker 전용 구성을 사용하기 위한 별칭을 설정하세요:

alias dockershadow='ss-local -c /etc/shadowsocks-libev/docker.json'

3 & 4 - app.yml을 깔끔하게 유지하기 위한 템플릿 생성

이것은 완전히 선택 사항이며 취향에 따라 다릅니다. 저는 app.yml을 가독성 있고 짧게 유지하는 것을 선호하며, 대신 다른 곳에서 구성 요소를 관리합니다. 취향에 따라 이름을 붙이세요. 저는 web.git.template.yml을 선택했습니다.

$ nano templates/web.git.template.yml
# 붙여넣기:

hooks:
  before_code:
    - exec:
       cmd:
         - git config --global http.proxy socks5://172.17.0.1:1080
         - git config --global https.proxy socks5://172.17.0.1:1080
         - git config --global https.sslVerify = false 

# 선택 사항
  after_code:
    - exec:
       cmd:
         - git config --global --unset http.proxy
         - git config --global --unset https.proxy
         - git config --global --unset https.sslVerify

after_web 훅으로 테스트해 보았지만, 효과가 없었습니다.

5 - app.yml 수정

app.yml에서 템플릿을 호출하세요:

$ cd /<discourse dir>
$ sudo nano containers/app.yml


templates:
  - "templates/web.template.yml"
  - "templates/web.china.template.yml"
  - "templates/web.ratelimited.template.yml"
  - "templates/web.socketed.template.yml"
  - "templates/web.git.template.yml"

템플릿 섹션은 아마도 다르게 보일 것입니다. web.china와 web.git-blabla(또는 당신이 지정한 이름) 템플릿을 포함하는지 확인하세요.
app.yml에서 1080:1080을 노출하지 마세요!

6 - 재빌드

재빌드하기 전에 git 클론 시 프록시 설정이 작동하는지 확인하세요.

$ git config --global http.proxy socks5://172.17.0.1:1080
$ git config --global https.proxy socks5://172.17.0.1:1080
$ git config --global https.sslVerify = false 

물론 이것은 홈 디렉토리의 사용자 .gitconfig에 프록시 플래그를 추가하므로, 테스트 후 이를 제거하는 것에 주의하세요.

Github에서 파일이 많은 무작위 대형 저장소를 선택하여 클론 속도를 확인하세요. 구성이 올바르면 Aliyun 설정에 따라 ~12-15 mb/s 속도로 클론할 수 있어야 합니다. 연결 속도가 200 kb/s에서 약 10 mb/s로 느리게 올라간다면, 당신의 시도는 성공적이지 못한 것입니다.

마지막으로 재빌드하세요:

$ cd /<discourse directory>

# 이전에 설정한 별칭을 사용하여 프록시 실행
$ dockershadow
$ ./launcher rebuild app

재빌드 과정은 자주 실패하므로 인내심이 필요합니다(그리고 아마도 바이주). app.yml에 설정한 플러그인이 적을수록 재빌드가 성공할 가능성이 높습니다.

7 - 비고

저는 이것을 여전히 우회책으로 보고, 프로덕션 환경에 적합한 절차로 보지 않습니다. 따라서 GitHub 저장소를 중국에 미러링하여 이 과정을 덜 고통스럽게 만드는 방법에 대한 아이디어가 있는 사람이 있을까요? 그리고 우리 모두 알다시피, GFW(중국 장거리 방화벽) 내부의 불투명한 메커니즘은 계속 변하고 있습니다.

물론 SOCKS5 프록시는 많은 옵션 중 하나일 뿐이지만, 저는 다목적 솔루션을 손에 갖추어 두는 것을 좋아합니다.

누군가 이 우회책을 프로덕션 환경에 적합하게 만드는 방법에 대한 아이디어가 있다면, 여러분의 의견을 환영합니다. Discourse는 훌륭한 소프트웨어이지만, 중국에서 널리 사용되지 않는 이유 중 하나는 번거로운 설치 및 유지보수 과정이라고 생각합니다. 지난 1년 동안 GUI를 통해 업그레이드를 시도해 보았지만, nGinx 리버스 프록시에 어떤 타임아웃 설정을 적용했든 100% 실패율을 기록했습니다.

중국어 번역은 뒤따를 것입니다

맞습니다. 이 인스턴스의 주요 목적이 회사 사내망 프레임워크의 일부가 되는 것이기 때문에, 지연 시간 문제로 인해 HK는 안타깝게도 선택지가 아닙니다. 또한, (예정된) 고객 대상 인스턴스는 중국 본토 사용자를 대상으로 할 것입니다 - 위챗 인증을 어떻게 처리할지 파악하는 대로, 중국 본토 알리바바 클라우드 존에서 작동할 수 있는 해결책이 필요합니다.

감사합니다. 이 주제와 관련된 여러 가이드를 확인해 보았지만, 문제의 근본적인 원인이 Git의 TLS 인증 자체보다는 GFW(대만방화벽)의 패킷 검사 과정에서의 핸드셰이크 검사에 있기 때문에 이 방식은 사용하지 않았습니다. git를 openssl과 함께 컴파일하면 고통으로 가득한 새로운 세계의 문이 열릴 수 있다는 것을 읽었기 때문입니다.

대부분의 테마 구성 요소는 빌드(또는 컨테이너 시작?) 시에도 GitHub에서 가져오기 때문에, git 프록시를 추가할 수 있는 다른 훅이 도움이 될 수 있습니다. GUI에서 작동하기를 원한다면 프록시를 제거하지 마세요. 그리고 redis-server가 원인이 되는 것 같지는 않습니다.

redis-server는 재구축 실패에 복잡성을 더한 또 다른 문제였습니다. 일종의 루프가 형성되었죠. 외부 redis 구성이 변경된 상태에서, 재구축 전 앱 상태는 해당 특정 redis 구성이 있어야만 시작할 수 있었기 때문입니다. 하지만 테마 구성 요소 가져오기가 작동하지 않아 재구축이 불가능했습니다.

다행히 20~30번의 재구축 실행 끝에 마침내 테마 구성 요소 업데이트가 가져와졌습니다.

전체 앱 설계 컨텍스트에서 보면, 플러그인이나 테마와 독립적으로 앱을 재구축하는 '안전 모드’에 대한 문서가 있으면 좋겠습니다. 테마 구성 요소를 처리하기 위한 훅(hook)이나 플러그인을 비활성화(설치 제거와 달리)하는 방법을 찾을 수 없었는데, 이는 다소 아쉬웠습니다.

수정: 와, 이제야 안전 모드 링크를 발견했습니다. 이전에 이 링크를 찾을 수 없었거든요(중국에서는 Google을 사용할 수 없으니, Bing으로 관련된 내용을 찾는 것이 쉽지 않습니다). 이 링크를 알았으면 정말 많이 도움이 되었을 텐데!

관리형 redis-server(예: Discourse with DO managed Redis - #3 by Falco)를 지정했는데, 재구성이 실패한 건가요?

Redis 문제는 부차적이었지만, 전체 Git 문제에 상당한 복잡성을 더했습니다. 위에서 자세히 설명한 글에서 알 수 있듯이, 저는 해당 문제를 해결했습니다.

그리고 네, 처음부터 분산 Redis 클러스터를 Discourse에 연결해 사용하고 있었습니다. 다만 관리형이 아니라, 다른 머신에 설치해 두었죠.

Redis 서버와의 연결 오류로 인해 앱이 시작되지 않아, GUI에서 테마 구성 요소를 비활성화할 수 없었습니다. 새로운 Redis 구성을 적용하려면 앱을 다시 빌드해야 했는데, GitHub 저장소에서 가져오기(fetch)가 실패하여 이를 수행할 수 없었습니다.

https://meta.discourse.org/t/a-fork-of-discourse-docker-repo-for-china

http 프록시 설정을 추가했음에도 불구하고 문제가 발생하는 경우,

GnuTLS recv error (-110): The TLS connection was non-properly terminated.

기존 해결책 외에도 템플릿에 아래 postBuffer 속성을 추가하면 제 문제가 해결되었습니다. gnutls-bin 패키지를 설치해야 합니다.

hooks:
  before_code:
    - exec:
       cmd:
         - apt-get update -y
         - apt-get install -y gnutls-bin
         - git config --global http.proxy socks5://172.17.0.1:1080
         - git config --global https.proxy socks5://172.17.0.1:1080
         - git config --global https.sslVerify false 
         - git config --global http.postBuffer 1048576000

# 선택 사항
  after_code:
    - exec:
       cmd:
         - git config --global --unset http.proxy
         - git config --global --unset https.proxy
         - git config --global --unset https.sslVerify
         - git config --global --unset http.postBuffer