두 클라이언트가 같은 노트북에 있고 같은 터널을 지나는데도 한쪽만 연결됩니다. 이 비교는 VPN이 선택적이라는 증거처럼 느껴지지만, 훨씬 흔한 설명은 두 클라이언트가 서로 다른 목적지를 지정한다는 것입니다. GUI는 자체 호스트, 포트, 역할, TLS 설정을 유지하고 필요할 때 조용히 SSH 포워드를 만드는 반면, psql은 셸이 이미 설정해 두었을 수 있는 libpq 기본값으로 연결을 조립합니다. 동작하는 연결을 재현하는 일은 GUI 설정을 읽는 문제이지 터널을 다시 설치하는 문제가 아닙니다.

신화: 동작하는 클라이언트 하나가 경로를 증명한다

GUI가 연결되면 터널이 트래픽을 나르며, GUI가 증명하는 것은 그뿐입니다. psql이 어느 주소를 해석하는지, 어떤 역할로 인증하는지, 여는 소켓이 실제로 머신을 떠나는지는 말해 주지 않습니다. 경로는 공유되지만 매개변수는 공유되지 않습니다.

로컬 포워드는 그 차이를 더 벌립니다. GUI는 SSH로 포워드한 포트의 127.0.0.1에 연결할 수 있고, 그 리스너는 GUI 세션이 유지되는 동안에만 존재합니다. 포워드 없이 localhost를 향한 psql은 VPN과 무관한 연결 거부를 받습니다.

GUI가 실제로 사용하는 엔드포인트를 읽으십시오

연결 속성을 열고 호스트, 포트, 데이터베이스, 역할 네 가지 값을 복사하십시오. 그다음 SSH 포워드가 사설 주소를 로컬 포트로 바꾸고 psql에 알려야 할 내용을 바꾸기 때문에 터널 탭을 찾으십시오.

SettingWhat the GUI holdsWhat psql uses when the value is absent
DestinationA saved hostname, sometimes behind a forwardA local Unix socket, not the remote endpoint
PortThe direct port or a forwarded local port5432, or the port from a service file
Role and databaseStored per connection profileYour operating-system user name
TLS identityIts own CA bundle and verification modeWhatever PGSSLMODE or the service file says
Startup filesNot read at allRead unless -X is passed

각 행은 서로 다른 계층에서 실패하며, 받는 메시지가 그 계층을 지목합니다. 저장된 프로필의 친숙한 이름을 psql에 붙여 넣어도 위의 값은 하나도 전달되지 않으며, 서비스 이름은 터미널이 결코 읽지 않을 수 있는 파일을 통해 해석됩니다.

libpq가 셸에서 물려받는 것

psql은 libpq 위의 얇은 클라이언트이고, libpq는 다른 무엇보다 먼저 환경을 읽습니다. PGHOST, PGHOSTADDR, PGPORT, PGDATABASE, PGUSER, PGSERVICE, PGSSLMODE 같은 변수는 각각 연결의 일부를 다른 곳으로 돌릴 수 있습니다. 이들은 입력되는 것이 아니라 상속되므로 실행한 명령에는 아무것도 나타나지 않습니다. 내보낸 PGSSLMODE 역시 명령줄에서 한마디 없이 클라이언트가 받아들이는 것을 바꿀 수 있습니다.

그중 두 변수가 먼저 의심받아 마땅합니다. PGHOSTADDR는 이름 해석을 덮어쓰는 숫자 목적지를 제공하고, 그동안 호스트 이름은 여전히 인증서 신원을 좌우하며, PGSERVICE는 서비스 파일에서 호스트, 데이터베이스, TLS 선택을 끌어옵니다. 이를 디버그하려고 환경 전체를 쏟아내는 것은 암호와 무관한 토큰이 들어 있을 수 있어 나쁜 습관이며, 대신 관심 있는 변수만 골라내십시오.

host와 hostaddr을 모두 생략하면 네트워크 주소가 아니라 Unix 도메인 소켓이 선택됩니다. 따라서 -h 없이 호출한 psql은 GUI가 쓰는 경로를 전혀 건드리지 않은 채 로컬에서 성공하거나 소켓 오류로 실패할 수 있습니다.

같은 매개변수를 직접 보내십시오

GUI의 값이 손에 있다면 명시적으로 전달하고 덮어쓰기를 추가할 수 있는 시작 파일은 건너뛰십시오. 아래 연결 문자열은 예시이므로 엔드포인트, 포트, 데이터베이스, 역할을 관리자가 승인한 값으로 바꾸고, 이미 동작하는 신뢰 구성을 유지하십시오.

psql -X "host=db.internal.example port=6432 dbname=orders user=report_ro sslmode=verify-full connect_timeout=5"

그 명령에는 세 가지 세부 사항이 의도적으로 들어 있습니다. -X는 psql의 시작 파일을 억제하지만 libpq의 환경이나 서비스 정의는 억제하지 않습니다. connect_timeout=5는 막다른 경로가 터미널을 몇 분씩 붙잡지 못하게 합니다. sslmode=verify-full는 인증서와 호스트 이름 검사를 켜 두며, TLS 오류가 나타날 때 사람들이 끄는 부분이 바로 이것입니다.

승인된 엔드포인트가 풀러 뒤에 있다면 포트 6432와 포트 5432가 서로 다른 서버 신원으로 같은 데이터에 도달할 수 있고, 검증은 데이터베이스 이름이 아니라 풀러 이름에서 실패할 수 있습니다. 모드를 약화하는 대신 연결 문자열의 이름을 고치십시오.

다음 단계는 오류 메시지가 고르게 하십시오

실패 문구는 시도가 어디까지 갔는지 알려 주며, 이는 터널에 대한 어떤 추측보다 낫습니다. 해석되지 않는 이름은 소켓이 생기기 전에 실패하고, 시간 초과는 경로나 방화벽이 패킷을 버렸다는 뜻이며, 거부는 무언가 응답하고 거절했다는 뜻이고, 인증 오류는 네트워크가 이미 동작했다는 뜻입니다.

psql -X -w "host=db.internal.example port=6432 dbname=orders user=report_ro sslmode=verify-full connect_timeout=5" -c '\conninfo'
env | grep -E '^PG'
psql --version

\conninfo의 출력을 GUI의 실제 세션과 비교하십시오. 같은 호스트, 같은 포트, 같은 역할, 같은 TLS 상태여야 하며, 이 네 가지가 일치하면 VPN이 더 설명할 것은 없습니다. 일치할 때까지 터널을 더 바꾸는 일은 터미널이 결코 만들지 않은 연결에 대한 추측입니다.

관련 글