Files
obsidian/services/searxng.md

160 lines
9.8 KiB
Markdown

---
title: SearXNG 검색 엔진
updated: 2026-06-10
tags: [search, proxy, google]
---
## 개요
SearXNG 메타 검색 엔진. K3s 클러스터(searxng 네임스페이스)에 Helm으로 배포.
- URL: https://searxng.inouter.com/
- Helm chart: 자체 차트 `gitea.inouter.com/kaffa/helm-charts``charts/searxng` (ArgoCD app `searxng`, repo는 k8s 아님 — 2026-06-10 정정)
- Values: `charts/searxng/values.yaml` (`proxy.address`로 google_proxy 주소 지정)
- 배포: ArgoCD GitOps (syncPolicy automated, prune+**selfHeal**). 라이브 패치는 self-heal로 되돌아가므로 git 소스를 고칠 것
- settings.yml: Secret `searxng-config` (ArgoCD가 values에서 렌더)
- 라우팅: [[gateway-api|Gateway API]] HTTPRoute (Ingress 사용 안 함)
## 라우팅 (Gateway API)
HTTPRoute로 외부 노출. [[gateway-api]] 참조.
- Gateway: `traefik-gateway` (kube-system)
- Host: `searxng.inouter.com`
- Backend: `searxng:8080`
- TLS: Gateway 레벨에서 종료 (`wildcard-inouter-com-tls`)
- Helm values에서 `ingress.main.enabled: false` (Ingress 비활성)
## Google 차단 우회 (tlsproxy)
Google이 SearXNG의 요청을 차단하는 두 가지 메커니즘:
1. **TLS fingerprint** — Python httpx의 TLS ClientHello가 봇으로 인식됨
2. **GSA User-Agent** — SearXNG가 사용하는 Google Search App UA가 특정 IP에서 차단됨
### tlsproxy
Go + [[https://github.com/refraction-networking/utls|utls]] 기반 MITM forward proxy. HTTP CONNECT 프록시로 동작하며, Google 도메인에 대해 Chrome TLS fingerprint로 연결을 중계함.
- 소스: **유실** (sandbox-tokyo `/tmp/tlsproxy/`는 NixOS 전환 시 소실, Gitea 미이관). 현재는 빌드된 `tlsproxy:latest` 이미지로만 존재 → **TODO: Dockerfile/소스를 Gitea로 복구**
- Docker 이미지: `tlsproxy:latest` (x86_64, 15.4MB). apisix-osaka에 보존본 있어 거기서 `docker save`로 배포
- 포트: 8443 (host network), 컨테이너 실행: `docker run -d --name tlsproxy --restart always --network host -v /opt/tlsproxy:/etc/tlsproxy tlsproxy:latest`
- CA 인증서: `/opt/tlsproxy/ca.crt` + `ca.key` (CN=`TLS Proxy CA`, fingerprint `C4:3E:98:7F...`, 2026-03-21 ~ 2036-03-18). **서버 간 동일 페어 공유**, K8s `searxng-ca-bundle` Secret에 이 CA 포함됨 (신뢰됨)
동작 방식:
1. SearXNG → HTTP CONNECT → tlsproxy
2. tlsproxy가 Google에 Chrome TLS fingerprint(utls `HelloChrome_Auto`, ALPN http/1.1)로 연결
3. 클라이언트에게 자체 CA로 서명한 인증서 제시
4. 양방향 relay
### 배포 현황
| 서버 | 상태 | Google IP | 비고 |
|------|------|--------|------|
| **zlambda** (100.78.51.18:8443) | **가동** (2026-06-10 재배포) | **200 (미차단)** | apisix-osaka에서 이미지+CA 이식. host network, restart=always. SearXNG google_proxy가 이 주소를 가리킴 |
| apisix-osaka (100.108.39.107:8443) | 가동(이미지/CA 보존본) | 403 차단 | IP 차단되어 Google엔 무효. tlsproxy 배포 소스로 활용 |
| relay4wd | 미배포 | 403 | IP 차단됨 |
| incus-jp1 | 미배포 | 403 | IP 차단됨 |
> **2026-06-10 재배포 기록**: 기존 SearXNG `proxy.address`가 죽은 IP `100.79.87.48:8443`을 가리켜 google 엔진이 `HTTP connection error`로 0건. apisix-osaka의 `tlsproxy:latest` 이미지와 CA 페어를 zlambda로 이식(`docker save|load` + scp), host network로 기동. `charts/searxng/values.yaml`의 `proxy.address`를 `100.78.51.18:8443`(zlambda 실제 tailscale IP)로 교정·푸시 → ArgoCD synced. 프록시 경유 Google 접근은 200/정상 HTML 복구됨(connection error 해소).
>
> **2026-06-10 google 0건 후속 조치 (위 재배포 직후)**:
>
> 정밀 진단 결과 — UA·파서 정합성:
> - 현재 이미지는 이미 최신 `2026.6.8`. 재pull 무의미 (버전 문제 아님).
> - `gen_gsa_useragent()`(Android Nexus5 GSA UA) + `Accept: */*` → Google이 **`data-ved` 풍부한 정상 HTML(150KB+, dv 50~80)** 반환. fetch→파서 전 구간 정상 (수동 재현 6~10건 추출 성공).
> - **Chrome UA → `data-ved` 없는 lite-shell(90KB) → 파서 0건.** 따라서 `uaPatch`(Chrome UA 교체)는 **해롭다 → 비활성화**. 기본 GSA UA가 정답. (uaPatch는 마운트만 되고 실행은 안 되던 inert 상태였음)
> - raw fetch 신뢰성: fresh/keepalive 모두 **10/10**. 연결 풀 문제 아님.
>
> 적용한 변경 (`charts/searxng`):
> - `uaPatch.enabled: false` (Chrome UA가 google 깨뜨림)
> - google 엔진 `timeout: 8 → 15`, outgoing `request_timeout: 10 → 15` + `max_request_timeout: 20`
> - CPU limit `500m → 1` (동시 부하 대비, 단 포함률엔 영향 없었음)
>
> 결과 — google 포함률: **0% → 격리 라이브 ~83%, 전체검색 ~40~50%**. 결과 품질은 정상.
>
> **남은 한계 — 근본 원인 규명 (2026-06-10 추가 조사)**: lite-shell은 **프록시 IP당 rate 기반**이다. 간격을 둔 단일 요청은 GSA·엔진형 파라미터 모두 data-ved HTML 100%(8/8) 반환하지만, 짧은 시간 내 다수 요청(burst)이면 Google이 해당 IP에 lite-shell(data-ved 없음)을 반환 → 파서 0건. 즉 정상 저빈도 사용은 대체로 정상, 고빈도/burst에서만 누락.
>
> **재시도 패치 시도 → 실패·롤백 (2026-06-10)**: google.py `response()`에 lite-shell 감지 시 동일 요청을 프록시로 백오프 재시도하는 래퍼를 주입(`googleRetryPatch`, configmap-google-patch + deployment command 래핑으로 실행 배선). 단일 강제-empty 테스트는 재시도로 10건 확보 성공했으나, **실사용 검증 실패**: lite-shell이 IP당 rate 기반이라 초기 요청과 재시도가 같은 rate-limit 윈도우를 공유 → 재시도가 무효일 뿐 아니라 Google 요청량을 (1+maxRetries)배로 늘려 rate-limit을 **악화**(burst 전체검색 포함률 0/12로 더 나빠짐). **결론: 단일 egress IP로는 클라이언트 재시도로 rate-limit을 이길 수 없다.** `googleRetryPatch.enabled: false`로 롤백, 템플릿은 보존.
>
> **~100% 달성의 진짜 조건**: egress IP 풀(여러 tlsproxy IP 로테이션). zlambda 단일 IP로는 한계. SearXNG 자체엔 빈-결과 in-search 재시도 없음(네트워크 `retries`는 HTTP 에러에만 동작). 현실적으로는 저빈도 사용 시 대부분 정상이므로 현 상태 수용 + brave/duckduckgo/qwant 보강으로 운용.
>
> 참고: SearXNG google 0건은 프로젝트 전반의 고질 이슈 (공식 #5286→PR#5644가 GSA UA로 전환한 것도 같은 맥락, 메인테이너가 "임시방편, 곧 패치될 것"이라 명시). #5867 HTTP 403, #5852 News 구조 변경 등.
### SearXNG 설정
`outgoing.networks``google_proxy` 네트워크를 정의하고, Google 엔진에서 `network: google_proxy`로 참조.
```yaml
outgoing:
networks:
google_proxy:
enable_http2: false
verify: /etc/ssl/custom/ca-certificates.crt
proxies: http://100.78.51.18:8443 # zlambda, 2026-06-10 가동 중 (charts/searxng/values.yaml proxy.address)
engines:
- name: google
engine: google
network: google_proxy
```
### CA 인증서 주입
Helm chart가 `extraVolumes`를 지원하지 않아 `kubectl patch`로 적용. **helm upgrade 시 재적용 필요.**
```bash
# CA 번들 Secret (시스템 CA + tlsproxy CA)
kubectl get secret searxng-ca-bundle -n searxng
# Deployment 패치 (helm upgrade 후 재실행)
kubectl patch deployment searxng -n searxng --type=json -p='[
{"op":"add","path":"/spec/template/spec/volumes/-","value":{"name":"ca-bundle","secret":{"secretName":"searxng-ca-bundle"}}},
{"op":"add","path":"/spec/template/spec/containers/0/volumeMounts/-","value":{"name":"ca-bundle","mountPath":"/etc/ssl/custom/ca-certificates.crt","subPath":"ca-certificates.crt","readOnly":true}},
{"op":"add","path":"/spec/template/spec/containers/0/env/-","value":{"name":"SSL_CERT_FILE","value":"/etc/ssl/custom/ca-certificates.crt"}},
{"op":"add","path":"/spec/template/spec/containers/0/env/-","value":{"name":"REQUESTS_CA_BUNDLE","value":"/etc/ssl/custom/ca-certificates.crt"}}
]'
```
### IP 차단 상태 확인 방법
서버에서 GSA UA로 Google 검색을 요청하여 확인:
```bash
curl -4 -s -o /dev/null -w '%{http_code}' --max-time 10 \
-H "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) GSA/406.0.862495628 Mobile/15E148 Safari/604.1" \
"https://www.google.com/search?q=test&hl=en-US"
# 200 = OK, 403 = 차단됨
```
### 주의사항
- **GSA UA 필수**: SearXNG Google 엔진 파서가 GSA 응답 형식에 의존. Chrome UA로 바꾸면 파싱 실패 (결과 0)
- **프록시 round-robin 불가**: SearXNG의 `proxies`를 dict(list) 형태로 넣으면 프록시를 무시하는 버그 있음. 단일 문자열만 동작
- **CA 번들 패치 영속성**: helm upgrade 시 volume mount/env 패치가 사라짐. upgrade 후 반드시 `kubectl patch` 재실행
- **새 프록시 서버 추가 시**: CA 키를 공유(`/opt/tlsproxy/ca.crt`, `ca.key`)하고, K8s Secret의 CA 번들도 갱신 필요
## 기타 프록시 (SOCKS5)
이전에 Google 우회 목적으로 배포했으나, TLS fingerprint 문제로 Google에는 무효. 다른 엔진이나 용도로는 사용 가능.
| 서버 | 포트 | 타입 |
|------|------|------|
| sandbox-tokyo | 1080 | (제거됨, 2026-04-08 NixOS 전환) |
| relay4wd | 1080 | Docker microsocks |
| incus-jp1 socks5-proxy | 1080 | Incus 컨테이너 microsocks |
| apisix-osaka | 1081, 1082 | Docker microsocks (IPv6 `1d00::1`, `1d01::1`) |
## Helm 업그레이드 절차
```bash
# 1. Helm upgrade
helm upgrade searxng searxng/searxng -n searxng -f ~/path/to/values.yaml
# 2. CA 번들 패치 재적용 (위 kubectl patch 명령)
# 3. 검증
kubectl exec -n searxng deploy/searxng -- wget -qO- \
"http://localhost:8080/search?q=test&format=json&engines=google" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d['results']), 'results')"
```