# 인프라 이관 가이드 (집 PC → IDC)

> 작성: 2026-05-12 / 대상 호스트: `chparkserver` (183.99.177.40)
> 목적: 운영 중인 서비스 전부 IDC 서버로 옮길 때 필요한 정보와 절차 정리

---

## 1. 현재 호스트 사양 / 환경

| 항목 | 값 |
|---|---|
| Hostname | `chparkserver` |
| OS | Ubuntu 24.04.4 LTS (kernel 6.8) |
| Docker | 29.1.3 |
| K3s | v1.34.6+k3s1 |
| 디스크 | `/` 116G / `/data` 916G (사용 121G) |
| 외부 IP | 183.99.177.40 |
| 접속 | SSH (chpark@) |

**디스크 사용량 분포 (`/data`)**:
```
51G  containerd  (K3s container runtime)
31G  docker      (docker engine data-root)
56G  opt         (docker compose 운영 파일들)
544M transfer
490M dumps
303M k3s
90M  mailu
87M  backups     ← 이번 사고 대응으로 만든 백업
```

---

## 2. 운영 중인 서비스 전체 목록

### 2.1 Docker Compose 기반 (직접 운영)

| Project | 경로 | 도메인/포트 | 비고 |
|---|---|---|---|
| **traefik** | `/data/opt/docker/traefik/` | 80, 443 | HTTPS reverse proxy (Let's Encrypt). 모든 도메인 입구 |
| **mailufinal** | `/data/mailu/` | mail.coa-soft.com (25/110/143/465/587/993/995) | Mailu 메일 서버 9개 컨테이너 |
| **nextcloudfinal** | `/home/chpark/nextcloud/` | cloud.junggomoa.com | Nextcloud + MariaDB 11.8 + Redis |
| **gitea2** | `/data/opt/docker/gitea/` | git.junggomoa.com / 2222 (SSH) | Gitea + PostgreSQL |
| **mattermost2** | `/data/opt/docker/mattermost/` | 8065 | Mattermost + PostgreSQL |
| **registry2** | `/data/opt/docker/registry/` | localhost:5000 | Docker registry (K3s 가 image pull 용) |
| **source** | `/home/chpark/momo-erp/source/` | momotogether.com | momo-erp |
| **tradeing2** | `/home/chpark/tradeing/` | (도메인 확인) | Tradeing 서비스 + PostgreSQL |
| **portainer** | `/data/opt/docker/portainer/` | 9000 (관리 UI) | Docker 관리 UI |

> ⚠️ **stopped 상태로 남은 옛 프로젝트**: `gitea`, `mailu`, `mattermost`, `nextcloud`, `registry`, `tradeing` — 이전 버전. **이관 시 무시**.

### 2.2 K3s 위에서 운영 (kubectl 로 관리)

| Namespace | 서비스 | 도메인 |
|---|---|---|
| `invyone` | frontend / backend-spring / backend-node | www.invyone.com / solution.invyone.com |
| `invyone-homepage` | homepage | (invyone homepage) |
| `insurance` | api / web / postgres-0 | (insurance 도메인) |
| `kubernetes-dashboard` | dashboard / metrics-scraper | (관리용) |
| `portainer` | portainer-agent | (관리용) |
| `kube-system` | coredns / metrics-server / local-path-provisioner | (인프라) |

> K3s manifests: `/data/k8s-manifests/` 또는 Gitea 의 repo 확인.

---

## 3. 데이터 볼륨 (이관 시 반드시 옮길 것)

### 3.1 Docker Named Volumes
```
nextcloud_nextcloud_db        ← Nextcloud DB (MariaDB 11.8)
nextcloud_nextcloud_data      ← Nextcloud 사용자 파일
insurance_postgres_data       ← Insurance K3s PostgreSQL
insurance_uploads             ← Insurance 업로드 파일
invyone-db-data               ← Invyone PostgreSQL
source_momo_data_storage      ← Momo ERP 데이터
```
호스트 경로: `/data/docker/volumes/<volume_name>/_data/`

### 3.2 Bind Mount 경로
```
/data/mailu/             ← Mailu compose + env + 인증서
/mailu/                  ← Mailu data (메일함 실제 저장 위치, /mailu/data)
/data/opt/docker/        ← 각 서비스의 compose 디렉토리
/home/chpark/nextcloud/  ← Nextcloud compose
/home/chpark/momo-erp/   ← Momo ERP compose + source
/home/chpark/tradeing/   ← Tradeing compose
```

### 3.3 K3s/containerd
```
/data/containerd/        ← K3s container runtime data (이관 비추, K3s 새로 설치 + manifest 재배포 권장)
/data/k3s/               ← K3s state
/data/k8s-manifests/     ← K8s manifest 파일들 (직접 이관)
```

---

## 4. 외부 노출 포트 (방화벽 규칙)

| 포트 | 프로토콜 | 용도 |
|---|---|---|
| 80, 443 | TCP | Traefik (HTTPS 리버스 프록시) |
| 25 | TCP | SMTP (메일 수신) |
| 465 | TCP | SMTPS (메일 송신 TLS) |
| 587 | TCP | SMTP Submission |
| 993 | TCP | IMAPS |
| 995 | TCP | POP3S |
| 110, 143 | TCP | POP3/IMAP plain (보안상 disable 권장) |
| 2222 | TCP | Gitea SSH |
| 8065 | TCP | Mattermost |
| 5432 | TCP | **⚠️ invyone-db PostgreSQL 외부 노출** — IDC 이관 시 내부 IP 만 허용하도록 변경 권장 |

---

## 5. 운영 중 적용된 Hotfix 내역 (이관 시 그대로 가져가야 함)

이관 시 단순히 compose 파일만 복사하면 안 됨. 아래는 컨테이너/네트워크 quirk 대응으로 추가한 패치.

### 5.1 Mailu (`/data/mailu/docker-compose.yml`)
- **admin / front / antispam** 의 `entrypoint` override:
  ```yaml
  entrypoint:
    - /bin/sh
    - -c
    - |
      echo "nameserver 127.0.0.11" > /etc/resolv.conf
      echo "nameserver 192.168.203.2" >> /etc/resolv.conf
      echo "search ." >> /etc/resolv.conf
      echo "options edns0 trust-ad ndots:0" >> /etc/resolv.conf
      exec /start.py
  ```
- **admin / front / antispam** 에 `extra_hosts` (mailu 내부 service IP 직접 박음):
  ```yaml
  extra_hosts:
    - "redis:192.168.203.4"
    - "imap:192.168.203.9"
    - "smtp:192.168.203.6"
    - "antispam:192.168.203.8"
    - "webmail:192.168.203.10"
    - "front:192.168.203.3"
    - "resolver:192.168.203.2"
  ```
  > 이유: docker user-defined bridge network 에서 admin 만 DNSSEC AD flag 강제. embedded DNS(127.0.0.11) 가 DNSSEC 안 함 → admin 시작 실패. mailu unbound resolver(192.168.203.2) 가 DNSSEC capable 이라 그쪽으로 박았고, internal service hostname 은 `extra_hosts` 로 박아 NXDOMAIN 방지.

- **resolver** service 의 `ipv4_address: 192.168.203.254` **제거됨** (자동 할당).
  > 이유: 192.168.203.254 IP 가 packet drop 되는 docker bridge quirk 발견. 자동 할당으로 .2 받음.

- **`/data/mailu/mailu.env`** 에 추가:
  ```
  WEBROOT_REDIRECT=/webmail/
  ```
  > 이유: 로그인 후 admin 계정이라 가끔 admin UI 로 redirect 됨. 항상 webmail 로 일관시킴.

### 5.2 Nextcloud (`/home/chpark/nextcloud/docker-compose.yml`)
- DB 컨테이너 image: `mariadb:10.5` → **`mariadb:11.8`** (원본 데이터 버전 일치)
- `command:` 라인 중복 제거 (이전엔 두 줄이라 첫 번째가 덮어써짐)
- 백업 위치: `/data/backups/nextcloud_db_20260512_080401.tgz` (87MB)
- 추가 mariadb-dump: `/data/backups/nc_db_*.sql`

### 5.3 Docker daemon (`/etc/docker/daemon.json`)
- 사용자가 이전에 추가한 `"dns": ["8.8.8.8", "1.1.1.1"]` 가 모든 컨테이너 dns 옵션과 합쳐져 충돌. **이관 시 daemon.json 의 dns 키는 빼는 게 안전** (Mailu 가 의존하는 docker embedded DNS 만 사용하도록).

### 5.4 cron
```
*/5 * * * * /mailu/sync-certs.sh
0 4 * * * /usr/bin/docker builder prune -af --filter until=72h
5 4 * * * /usr/bin/docker image prune -af --filter until=168h
```
이관 시 root crontab 그대로 복사. `sync-certs.sh` 는 traefik 발급 인증서를 Mailu 형식으로 변환하는 스크립트.

---

## 6. 이관 절차 (체크리스트)

### Phase 1 — 신규 IDC 호스트 준비
- [ ] Ubuntu 24.04 LTS 설치 (현재와 동일 버전 권장)
- [ ] Docker 29.x + Docker Compose v2 설치
- [ ] K3s 설치 (`curl -sfL https://get.k3s.io | sh -`)
- [ ] 디스크 파티션: `/` (시스템) + `/data` (운영 데이터, **916G 이상 권장**)
- [ ] systemd: `docker`, `k3s`, `containerd` 자동 시작 enable

### Phase 2 — 데이터 백업 (현재 호스트)
```bash
# 1. 모든 docker compose 정지 (downtime 시작)
for proj in mailufinal nextcloudfinal gitea2 mattermost2 registry2 source tradeing2 traefik portainer; do
  docker compose -p $proj down
done

# 2. K3s 도 정지 (시작 시 컨테이너 자동 stop)
sudo systemctl stop k3s

# 3. volume + 운영 디렉토리 통째로 백업
sudo tar -czf /data/backups/all_volumes_$(date +%Y%m%d).tgz \
  /data/docker/volumes \
  /data/mailu \
  /data/opt \
  /data/k8s-manifests \
  /home/chpark/nextcloud \
  /home/chpark/momo-erp \
  /home/chpark/tradeing \
  /mailu

# 4. crontab 백업
sudo crontab -l > /data/backups/root_crontab.txt

# 5. daemon.json 백업
sudo cp /etc/docker/daemon.json /data/backups/daemon.json
```

### Phase 3 — IDC 로 전송
```bash
# rsync 권장 (재시도 가능)
rsync -avzP --partial --append-verify \
  /data/backups/all_volumes_*.tgz \
  user@idc-host:/data/backups/
```

### Phase 4 — IDC 에서 복원
```bash
cd /
sudo tar -xzf /data/backups/all_volumes_*.tgz

# crontab 복원
sudo crontab /data/backups/root_crontab.txt

# 각 서비스 기동
for path in /data/opt/docker/traefik /data/mailu /home/chpark/nextcloud /data/opt/docker/gitea /data/opt/docker/mattermost /data/opt/docker/registry /home/chpark/momo-erp/source /home/chpark/tradeing /data/opt/docker/portainer; do
  cd "$path" && docker compose up -d
done

# K3s manifest 재적용
kubectl apply -f /data/k8s-manifests/
```

### Phase 5 — DNS 변경
- 도메인 A 레코드를 새 IDC IP 로 변경 (TTL 짧게 미리 줄여놓으면 좋음)
  - `mail.coa-soft.com`
  - `cloud.junggomoa.com`
  - `git.junggomoa.com`
  - `momotogether.com`
  - `www.invyone.com` / `solution.invyone.com`
  - 기타
- Mailu MX 레코드도 새 IP 로

### Phase 6 — 검증
```bash
# 컨테이너 상태
docker ps --filter "name=mailufinal\|nextcloud\|traefik\|gitea" --format "{{.Names}} {{.Status}}"

# HTTP 확인
for url in https://mail.coa-soft.com/ https://cloud.junggomoa.com/ https://git.junggomoa.com/ https://momotogether.com/; do
  curl -ksS -o /dev/null -w "$url -> %{http_code}\n" "$url"
done

# SMTP banner
echo "QUIT" | nc -w 3 새IDC_IP 25
```

---

## 7. 이관 후 정리할 것 (선택)

- [ ] 옛 compose project (gitea/mailu/mattermost/nextcloud/registry/tradeing) 삭제 — `docker compose -p <이름> down --remove-orphans`
- [ ] dangling image 정리 — 이미 cron 으로 자동화됨
- [ ] `/data/backups/` 의 오래된 tarball 정리
- [ ] `daemon.json` 의 `"dns"` 키 제거 (Mailu admin DNS 충돌 방지)
- [ ] Mailu admin/front/antispam 의 entrypoint override 가 더 이상 필요한지 재확인 (IDC 의 docker network 환경에서는 안 필요할 수도)

---

## 8. 핵심 비밀번호 / 인증 정보

> ⚠️ 이 문서에는 비밀번호 직접 안 박는다. 아래 위치에서 확인:

- Mailu 계정: `/data/mailu/mailu.env` (SECRET_KEY, DB_PW 등)
- Nextcloud DB: `/home/chpark/nextcloud/docker-compose.yml` (MYSQL_ROOT_PASSWORD)
- 각 서비스 admin 계정: 해당 compose 의 environment 또는 env_file
- Traefik basic auth: `/data/opt/docker/traefik/` 안 설정
- SSH 비밀번호: 운영 PC 관리자가 보관

이관 시 새 IDC 에서 비밀번호 **모두 회전(rotation) 권장**.

---

## 9. 알려진 이슈 / 주의사항

1. **Mailu resolver 192.168.203.254 IP 회피**: 이번 사고에서 .254 IP 가 packet drop 되는 docker bridge quirk 발견. IDC 에서도 발생할 수 있으니 resolver 는 자동 할당으로 두는 게 안전.

2. **MariaDB 버전 일치**: Nextcloud DB 는 11.8.x. 이관 시 절대 다운그레이드 금지 (이번에 10.5 로 갔다가 plugin load fail 로 사고남).

3. **K3s 와 docker 의 iptables/nftables 공존**: K3s 의 kube-router 가 FORWARD chain 에 끼어들어서 docker bridge 통신에 영향을 줄 수 있음. mailu 의 192.168.203.0/24 subnet 이 다른 서비스의 K3s pod CIDR 와 안 겹치는지 확인.

4. **메일 데이터 위치**: 사용자 메일함 파일은 `/mailu/data/` (호스트 root). `/data/mailu/` 와 다른 위치. 이관 시 둘 다 옮겨야 함.

5. **인증서**: Let's Encrypt 인증서는 Traefik 이 자동 발급. 이관 후 도메인 가리키면 자동 재발급. 단 rate limit 주의 (도메인당 주 5건).

---

## 10. 빠른 참조 — 자주 쓰는 명령

```bash
# 전체 컨테이너 상태
docker ps --format "{{.Names}}\t{{.Status}}" | sort

# 특정 서비스 로그
docker logs --tail 50 mailufinal-admin-1

# 한 서비스만 재기동
cd /data/mailu && docker compose up -d --force-recreate admin

# Mailu 계정 추가 (admin 컨테이너 안)
docker exec mailufinal-admin-1 flask mailu user $USER $DOMAIN $PASSWORD

# Nextcloud occ
docker exec -u www-data nextcloud-app-new php occ status

# K3s pod 상태
kubectl get pods -A
```
