Add troubleshooting docs: Caddy+Headscale proxy, NAT hairpinning, ODBC conflict
- caddy-headscale-proxy.md: HTTP redirect + HTTP/2 Upgrade header conflict fix - nat-hairpinning.md: Same-LAN public IP access issue and auto-detection - odbc-freetds-conflict.md: FreeTDS vs ODBC Driver 18 for MSSQL named instances - README.md: Install script commands (individual + unified) and doc index Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
42
README.md
42
README.md
@@ -1,3 +1,41 @@
|
||||
# infra-troubleshooting
|
||||
# PharmQ 인프라 트러블슈팅 & 설치 가이드
|
||||
|
||||
PharmQ 인프라 고급 트러블슈팅 기록
|
||||
약국 인프라 설치/운영 중 발생하는 고급 트러블슈팅 기록 및 설치 스크립트 문서.
|
||||
|
||||
## 설치 스크립트
|
||||
|
||||
### 개별 실행 (단계별)
|
||||
```bash
|
||||
# 1단계: PVE 구독 제한 해제
|
||||
curl -fsSL https://git.0bin.in/thug0bin/pve9-repo-fix/raw/branch/main/fix-pve9-repos.sh | bash
|
||||
|
||||
# 2단계: Headscale VPN 등록 + 약국 생성
|
||||
curl -fsSL https://git.0bin.in/thug0bin/pve9-repo-fix/raw/branch/main/headscale-auto-register.sh | bash
|
||||
|
||||
# 3단계: PBS 등록 + VM 복원
|
||||
curl -fsSL https://git.0bin.in/thug0bin/pve9-repo-fix/raw/branch/main/pbs_allinone.sh | bash
|
||||
```
|
||||
|
||||
### 통합 실행 (원클릭)
|
||||
```bash
|
||||
# 전체 통합: Repo Fix → VPN → PBS → CT 생성 → 약국/장비/계정 등록
|
||||
curl -fsSL https://git.0bin.in/thug0bin/pve9-repo-fix/raw/branch/main/pharmq-setup.sh | bash
|
||||
```
|
||||
|
||||
통합 스크립트 8단계:
|
||||
1. PVE Repository Fix (구독 제한 해제)
|
||||
2. PVE Host Tailscale → Headscale 등록
|
||||
3. 약국 정보 수집 (대화형)
|
||||
4. PBS 등록 + Windows VM(팜IT3000) 복원
|
||||
5. Ubuntu CT 자동 생성
|
||||
6. CT 내부 환경 구축 (ODBC, Tailscale, API)
|
||||
7. farmq.db 약국 + 장비 2개 + gateway 계정 등록
|
||||
8. 검증 + 로그인 정보 출력
|
||||
|
||||
## 트러블슈팅 문서
|
||||
|
||||
| 문서 | 주제 | 날짜 |
|
||||
|------|------|------|
|
||||
| [caddy-headscale-proxy.md](caddy-headscale-proxy.md) | Caddy + Headscale 리버스 프록시 — HTTP 리다이렉트 + HTTP/2 Upgrade 충돌 | 2026-04-06 |
|
||||
| [nat-hairpinning.md](nat-hairpinning.md) | 같은 LAN에서 공인 IP 접근 시 NAT Hairpinning 문제 | 2026-04-06 |
|
||||
| [odbc-freetds-conflict.md](odbc-freetds-conflict.md) | MSSQL named instance — FreeTDS vs ODBC Driver 18 충돌 | 2026-04-06 |
|
||||
|
||||
133
caddy-headscale-proxy.md
Normal file
133
caddy-headscale-proxy.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Caddy + Headscale 리버스 프록시 트러블슈팅
|
||||
|
||||
> 날짜: 2026-04-06
|
||||
> 환경: Caddy v2.11.2, Headscale 0.26.1 (Docker), Tailscale 1.96.4
|
||||
|
||||
## 증상
|
||||
|
||||
외부 노드에서 `tailscale up --login-server=http://head.pharmq.kr` 실행 시 무한 대기 또는 실패.
|
||||
|
||||
- PBS-LXC, 약국 PVE 등 외부 노드에서 Headscale 등록 불가
|
||||
- Headscale Docker 재시작 후 기존 노드 재연결 실패
|
||||
- Caddy 로그에 502 Bad Gateway 다수 발생
|
||||
|
||||
## 원인: 2가지 문제
|
||||
|
||||
### 문제 1: HTTP → HTTPS 강제 리다이렉트 (308)
|
||||
|
||||
**Caddy 동작**: TLS 설정이 있는 도메인은 자동으로 HTTP(80) → HTTPS(443) 308 리다이렉트 생성
|
||||
|
||||
```
|
||||
# Tailscale 클라이언트 요청
|
||||
POST http://head.pharmq.kr/ts2021
|
||||
|
||||
# Caddy 응답
|
||||
HTTP/1.1 308 Permanent Redirect
|
||||
Location: https://head.pharmq.kr/
|
||||
Server: Caddy
|
||||
```
|
||||
|
||||
**왜 문제인가**: Tailscale 클라이언트는 `--login-server=http://...`로 설정되면 HTTP를 기대함. HTTPS 리다이렉트를 자동으로 따라가지 못함.
|
||||
|
||||
**해결**: `http://` 전용 블록을 별도로 추가
|
||||
|
||||
```caddy
|
||||
http://head.pharmq.kr {
|
||||
reverse_proxy 192.168.0.100:8070
|
||||
}
|
||||
```
|
||||
|
||||
### 문제 2: HTTP/2에서 Upgrade 헤더 호환 불가 (502)
|
||||
|
||||
**Caddy 동작**: HTTPS 연결에서 백엔드로 프록시할 때 HTTP/2를 사용
|
||||
|
||||
```
|
||||
# Tailscale TS2021 프로토콜 요청
|
||||
POST /ts2021 HTTP/1.1
|
||||
Connection: upgrade
|
||||
Upgrade: tailscale-control-protocol
|
||||
|
||||
# Caddy가 h2로 변환하여 백엔드에 전달 → 에러
|
||||
http2: invalid Upgrade request header: ["tailscale-control-protocol"]
|
||||
→ 502 Bad Gateway
|
||||
```
|
||||
|
||||
**왜 문제인가**: HTTP/2 스펙(RFC 7540)에서 `Connection`과 `Upgrade` 헤더는 hop-by-hop이라 금지됨. Tailscale TS2021은 HTTP/1.1 Upgrade 메커니즘에 의존.
|
||||
|
||||
**Caddy 로그 (실제)**:
|
||||
```json
|
||||
{
|
||||
"level": "error",
|
||||
"msg": "http2: invalid Upgrade request header: [\"tailscale-control-protocol\"]",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"uri": "/ts2021",
|
||||
"headers": {
|
||||
"Connection": ["upgrade"],
|
||||
"Upgrade": ["tailscale-control-protocol"]
|
||||
}
|
||||
},
|
||||
"status": 502
|
||||
}
|
||||
```
|
||||
|
||||
**해결**: 백엔드 연결을 HTTP/1.1로 강제 + 스트리밍 즉시 전달
|
||||
|
||||
```caddy
|
||||
head.pharmq.kr {
|
||||
reverse_proxy 192.168.0.100:8070 {
|
||||
flush_interval -1
|
||||
transport http {
|
||||
versions 1.1
|
||||
}
|
||||
}
|
||||
tls {
|
||||
dns cloudflare {env.CF_API_TOKEN}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 최종 Caddyfile 설정
|
||||
|
||||
```caddy
|
||||
# Headscale — HTTP (리다이렉트 없이 직접 프록시)
|
||||
http://head.pharmq.kr {
|
||||
reverse_proxy 192.168.0.100:8070
|
||||
}
|
||||
|
||||
# Headscale — HTTPS (HTTP/1.1 강제, TS2021 Upgrade 호환)
|
||||
head.pharmq.kr {
|
||||
reverse_proxy 192.168.0.100:8070 {
|
||||
flush_interval -1
|
||||
transport http {
|
||||
versions 1.1
|
||||
}
|
||||
}
|
||||
|
||||
tls {
|
||||
dns cloudflare {env.CF_API_TOKEN}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 검증 방법
|
||||
|
||||
```bash
|
||||
# HTTP 프록시 확인 (리다이렉트 없어야 함)
|
||||
curl -v http://head.pharmq.kr 2>&1 | grep -E "HTTP|Location|Server"
|
||||
# 기대: HTTP/1.1 404 Not Found, Server 없거나 Via: 1.1 Caddy
|
||||
|
||||
# HTTPS 프록시 확인
|
||||
curl -sk https://head.pharmq.kr/ts2021 2>&1 | head -5
|
||||
# 502가 아니어야 함
|
||||
|
||||
# Tailscale 등록 테스트
|
||||
tailscale up --login-server=http://head.pharmq.kr \
|
||||
--authkey=<KEY> --accept-routes --accept-dns=false
|
||||
```
|
||||
|
||||
## 참고
|
||||
|
||||
- Docker 포트 매핑: 호스트 8070 → 컨테이너 8080
|
||||
- Headscale config: `listen_addr: 0.0.0.0:8080`
|
||||
- 같은 LAN에서는 NAT Hairpinning 문제로 `http://192.168.0.100:8070` 직접 사용 필요 ([nat-hairpinning.md](nat-hairpinning.md) 참조)
|
||||
72
nat-hairpinning.md
Normal file
72
nat-hairpinning.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# NAT Hairpinning (NAT Loopback) 문제
|
||||
|
||||
> 날짜: 2026-04-06
|
||||
> 환경: 같은 LAN 내 PVE에서 공인 IP 도메인 접근 시
|
||||
|
||||
## 증상
|
||||
|
||||
같은 LAN(192.168.0.x)에 있는 PVE에서 `http://head.pharmq.kr` 접속 시:
|
||||
- Caddy가 아닌 **공유기 웹 관리 페이지**가 응답
|
||||
- `tailscale up --login-server=http://head.pharmq.kr` 무한 대기
|
||||
|
||||
```bash
|
||||
# 같은 LAN에서 실행
|
||||
curl -v http://head.pharmq.kr 2>&1 | head -10
|
||||
# 결과: 공유기 login.cgi로 리다이렉트
|
||||
# <meta http-equiv=refresh content="0; URL=login/login.cgi">
|
||||
```
|
||||
|
||||
## 원인
|
||||
|
||||
`head.pharmq.kr` → DNS → `118.44.36.28` (공인 IP) → 공유기
|
||||
|
||||
같은 공유기 뒤에 있는 장비가 자기 공유기의 공인 IP로 접근하면:
|
||||
1. 패킷이 공유기로 감
|
||||
2. 공유기가 "이건 내 IP" 판단 → 내부 포트포워딩 대신 자기 웹UI 응답
|
||||
3. Caddy(192.168.0.19)까지 도달 안 함
|
||||
|
||||
이것이 **NAT Hairpinning** (=NAT Loopback) 문제.
|
||||
|
||||
## 영향 범위
|
||||
|
||||
| 상황 | 문제 발생? |
|
||||
|------|-----------|
|
||||
| 같은 공유기 뒤 (같은 LAN) | **O** — 공유기가 가로챔 |
|
||||
| 같은 IP 대역이지만 다른 공유기 (외부 약국) | **X** — 인터넷 경유 정상 |
|
||||
| 완전 다른 네트워크 (외부) | **X** — 정상 |
|
||||
|
||||
## 해결 방법
|
||||
|
||||
### 방법 1: 스크립트에서 자동 감지 (채택)
|
||||
|
||||
```bash
|
||||
# Headscale 8070 포트에 실제 응답하는지 확인
|
||||
if curl -s --connect-timeout 3 -o /dev/null -w "%{http_code}" \
|
||||
http://192.168.0.100:8070/ 2>/dev/null | grep -q "200\|404"; then
|
||||
HEADSCALE_SERVER="http://192.168.0.100:8070" # LAN 직접
|
||||
else
|
||||
HEADSCALE_SERVER="http://head.pharmq.kr" # 외부 도메인
|
||||
fi
|
||||
```
|
||||
|
||||
ping으로 판단하면 안 됨 — 외부 약국에도 192.168.0.100 장비가 있을 수 있음. **Headscale 포트 응답**으로 판단해야 정확.
|
||||
|
||||
### 방법 2: 공유기 NAT Loopback 활성화
|
||||
|
||||
대부분의 가정용/ISP 공유기는 미지원:
|
||||
|
||||
| 공유기 | NAT Loopback 지원 |
|
||||
|--------|-------------------|
|
||||
| ipTIME (한국 약국 대부분) | X 또는 숨김 설정 |
|
||||
| KT/SKB/LG U+ 기본 공유기 | X |
|
||||
| MikroTik, pfSense, UniFi | O |
|
||||
|
||||
### 방법 3: 내부 DNS 오버라이드
|
||||
|
||||
공유기 DNS에서 `head.pharmq.kr` → `192.168.0.19`(Caddy LAN IP)로 강제.
|
||||
모든 공유기에 일일이 설정해야 해서 비현실적.
|
||||
|
||||
## 참고
|
||||
|
||||
- 통합 설치 스크립트(`pharmq-setup.sh`)에 자동 감지 로직 포함됨
|
||||
- [caddy-headscale-proxy.md](caddy-headscale-proxy.md)와 함께 발생할 수 있음
|
||||
69
odbc-freetds-conflict.md
Normal file
69
odbc-freetds-conflict.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# MSSQL Named Instance — FreeTDS vs ODBC Driver 18 충돌
|
||||
|
||||
> 날짜: 2026-04-06
|
||||
> 환경: Ubuntu 24.04 LXC, pyodbc, MSSQL Server named instance
|
||||
|
||||
## 증상
|
||||
|
||||
Ubuntu CT에서 Flask API 서버가 MSSQL에 연결 실패:
|
||||
```
|
||||
[FreeTDS][SQL Server]Unable to connect to data source
|
||||
PM_BASE 연결 실패
|
||||
```
|
||||
|
||||
## 원인
|
||||
|
||||
### Named Instance 접속 구조
|
||||
|
||||
팜IT3000 MSSQL 서버: `192.168.0.201\PM2014`
|
||||
|
||||
Named instance는 SQL Server Browser 서비스를 통해 **동적 포트**를 할당받음:
|
||||
1. 클라이언트 → SQL Server Browser (UDP 1434) → "PM2014는 포트 49172"
|
||||
2. 클라이언트 → SQL Server (TCP 49172) → 연결
|
||||
|
||||
**FreeTDS는 이 프로토콜을 지원하지 않음** — 고정 포트만 가능.
|
||||
|
||||
### 드라이버 선택 우선순위 문제
|
||||
|
||||
`dbsetup.py`에서 Linux 드라이버 우선순위:
|
||||
```python
|
||||
linux_drivers = [
|
||||
"ODBC Driver 18 for SQL Server",
|
||||
"ODBC Driver 17 for SQL Server",
|
||||
"FreeTDS", # ← preferred_drivers 맨 앞에 삽입됨
|
||||
]
|
||||
```
|
||||
|
||||
`tdsodbc` 패키지가 설치되면 `pyodbc.drivers()`에서 FreeTDS가 감지되어 ODBC Driver 18보다 **먼저 선택됨**.
|
||||
|
||||
```
|
||||
Before: pyodbc.drivers() → ['FreeTDS', 'ODBC Driver 18 for SQL Server']
|
||||
→ FreeTDS 선택 → named instance 접속 실패
|
||||
```
|
||||
|
||||
## 해결
|
||||
|
||||
```bash
|
||||
# 1. Microsoft ODBC Driver 18 설치
|
||||
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | \
|
||||
gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg
|
||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-prod.gpg] \
|
||||
https://packages.microsoft.com/ubuntu/24.04/prod noble main" \
|
||||
> /etc/apt/sources.list.d/mssql-release.list
|
||||
apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql18
|
||||
|
||||
# 2. FreeTDS ODBC 드라이버 제거 (충돌 방지)
|
||||
apt-get remove -y tdsodbc
|
||||
odbcinst -u -d -n FreeTDS
|
||||
```
|
||||
|
||||
```
|
||||
After: pyodbc.drivers() → ['ODBC Driver 18 for SQL Server']
|
||||
→ ODBC Driver 18 선택 → named instance 정상 접속
|
||||
```
|
||||
|
||||
## 주의사항
|
||||
|
||||
- `tdsodbc` 패키지만 제거. `unixodbc`, `freetds-dev` 등 라이브러리는 유지 가능
|
||||
- 새 약국 CT 셋업 시 **절대 `tdsodbc` 설치하지 않을 것**
|
||||
- 통합 설치 스크립트(`pharmq-setup.sh`) Phase 6에서 자동 처리됨
|
||||
Reference in New Issue
Block a user