docs: add lab operations guide with tooling, commands and drills
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bc784fcd6e
commit
61ba5db259
@@ -9,6 +9,7 @@
|
|||||||
|---|---|
|
|---|---|
|
||||||
| [`docs/session-store-lab-roadmap.md`](../../docs/session-store-lab-roadmap.md) | 이 축의 계획과 진행 상황 |
|
| [`docs/session-store-lab-roadmap.md`](../../docs/session-store-lab-roadmap.md) | 이 축의 계획과 진행 상황 |
|
||||||
| [`docs/session-lab-concepts.md`](../../docs/session-lab-concepts.md) | 등장 개념 전체 |
|
| [`docs/session-lab-concepts.md`](../../docs/session-lab-concepts.md) | 등장 개념 전체 |
|
||||||
|
| [`docs/session-lab-operations.md`](../../docs/session-lab-operations.md) | 관측 도구 · 자주 쓰는 명령 · 훈련 |
|
||||||
| [`docs/two-hop-proxy-header-contract.md`](../../docs/two-hop-proxy-header-contract.md) | 첫 실험 결과 |
|
| [`docs/two-hop-proxy-header-contract.md`](../../docs/two-hop-proxy-header-contract.md) | 첫 실험 결과 |
|
||||||
|
|
||||||
## 토폴로지
|
## 토폴로지
|
||||||
|
|||||||
@@ -0,0 +1,359 @@
|
|||||||
|
# 실험대 운영 — 도구 · 명령 · 훈련
|
||||||
|
|
||||||
|
실험을 돌리는 데 반복해서 쓰는 것들. 개념은
|
||||||
|
[`session-lab-concepts.md`](session-lab-concepts.md), 계획은
|
||||||
|
[`session-store-lab-roadmap.md`](session-store-lab-roadmap.md)에 있다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 관측 도구
|
||||||
|
|
||||||
|
### htop
|
||||||
|
|
||||||
|
**설치** — 세 대 모두 배포판이 다르다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# lab host (Arch)
|
||||||
|
sudo pacman -S htop
|
||||||
|
|
||||||
|
# 게스트 (Debian) — lab host 에서 한 번에
|
||||||
|
for n in kc-lab-1 kc-lab-2; do
|
||||||
|
ssh $n 'sudo apt-get update -qq && sudo apt-get install -y -qq htop'
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
**lab host에서 htop을 읽는 법 — VM은 프로세스로 보인다**
|
||||||
|
|
||||||
|
가장 중요한 관점이다. 게스트는 **호스트 입장에서 `qemu-system-x86_64`
|
||||||
|
프로세스 하나**다. 그래서 VM의 메모리 사용량이 곧 그 프로세스의 RES다.
|
||||||
|
|
||||||
|
```
|
||||||
|
pid=4677 RSS=3765MB qemu-system-x86 ← kc-lab-1 (할당 3584M)
|
||||||
|
pid=4770 RSS=2670MB qemu-system-x86 ← kc-lab-2 (할당 2560M)
|
||||||
|
```
|
||||||
|
|
||||||
|
**RSS가 할당량보다 조금 큰 이유**는 QEMU 자체의 에뮬레이션 오버헤드
|
||||||
|
(장치 모델, 버퍼)가 더해지기 때문이다. 게스트가 터치한 페이지만큼만
|
||||||
|
RSS로 잡히므로, 게스트가 메모리를 더 쓰면 RSS도 할당 상한까지 올라간다.
|
||||||
|
|
||||||
|
**htop 안에서 쓸 키**
|
||||||
|
|
||||||
|
| 키 | 동작 | 이 실험대에서 |
|
||||||
|
|---|---|---|
|
||||||
|
| `F5` | 트리 뷰 | `libvirtd` → `qemu-system` 계층 확인 |
|
||||||
|
| `F4` | 필터 | `qemu`만 보기 |
|
||||||
|
| `F6` | 정렬 기준 | `PERCENT_MEM`으로 정렬 |
|
||||||
|
| `M` / `P` | 메모리/CPU 정렬 | |
|
||||||
|
| `u` | 사용자 필터 | `libvirt-qemu`로 VM만 |
|
||||||
|
| `H` | 스레드 숨김 | QEMU는 vCPU마다 스레드라 켜두면 지저분하다 |
|
||||||
|
|
||||||
|
**게스트 안에서 htop을 읽을 때** 보이는 것은 `k3s`(server 또는 agent),
|
||||||
|
`containerd`, `containerd-shim`, 그리고 각 파드의 `java` 프로세스다.
|
||||||
|
Java 힙 상한은 컨테이너 limit의 70%(`-XX:MaxRAMPercentage=70`)이므로
|
||||||
|
512Mi limit이면 약 358Mi다.
|
||||||
|
|
||||||
|
### k9s
|
||||||
|
|
||||||
|
설치는 되어 있다. 별도 구성 없이 `~/.kube/config`를 읽는다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
k9s
|
||||||
|
```
|
||||||
|
|
||||||
|
| 키 | 동작 |
|
||||||
|
|---|---|
|
||||||
|
| `:` | 명령 모드 — `:pods` `:svc` `:ing` `:nodes` `:events` |
|
||||||
|
| `0` | 전체 네임스페이스 |
|
||||||
|
| `/` | 필터 |
|
||||||
|
| `l` | 로그 |
|
||||||
|
| `d` | describe |
|
||||||
|
| `y` | YAML |
|
||||||
|
| `s` | 파드 안 셸 |
|
||||||
|
| `Ctrl+d` | 파드 삭제 ← **장애 주입에 씀** |
|
||||||
|
| `esc` / `q` | 뒤로 / 종료 |
|
||||||
|
|
||||||
|
`~/.config/k9s/config.yaml`의 `refreshRate`를 2초로 낮추면 노드를 죽였을 때
|
||||||
|
파드 재배치가 실시간으로 보인다.
|
||||||
|
|
||||||
|
### kubectl top
|
||||||
|
|
||||||
|
k3s가 metrics-server를 기본 배포하므로 바로 쓸 수 있다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl top nodes
|
||||||
|
kubectl -n header-lab top pods
|
||||||
|
```
|
||||||
|
|
||||||
|
**htop과 보는 층이 다르다.**
|
||||||
|
|
||||||
|
| | 보는 것 |
|
||||||
|
|---|---|
|
||||||
|
| `htop` (lab host) | VM 프로세스 = 게스트 전체 |
|
||||||
|
| `htop` (게스트) | 게스트 안의 프로세스 |
|
||||||
|
| `kubectl top` | 파드·노드 단위, 클러스터 관점 |
|
||||||
|
|
||||||
|
### 상태 점검 스크립트
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./deploy/lab/scripts/verify-lab.sh # lab host 에서
|
||||||
|
./deploy/lab/scripts/measure-proxy-headers.sh # 어디서든
|
||||||
|
```
|
||||||
|
|
||||||
|
`verify-lab.sh`는 게스트·k3s·nginx·인증서·공개 진입점을 한 번에 확인하고
|
||||||
|
`lab is healthy`를 출력한다. **`404`가 성공 신호**다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 자원 실측과 예산
|
||||||
|
|
||||||
|
**2026-09-03 기준, Keycloak 배포 전**
|
||||||
|
|
||||||
|
```
|
||||||
|
lab host 총 7628MB · 사용 7227MB · 여유 400MB (+ swap 8GB)
|
||||||
|
├ qemu #1 RSS 3765MB kc-lab-1
|
||||||
|
└ qemu #2 RSS 2670MB kc-lab-2
|
||||||
|
|
||||||
|
kc-lab-1 CPU 4% 메모리 1774Mi / 3.4GB (51%)
|
||||||
|
kc-lab-2 CPU 1% 메모리 1032Mi / 2.4GB (41%)
|
||||||
|
echo 파드 각 150Mi
|
||||||
|
```
|
||||||
|
|
||||||
|
**호스트 여유가 400MB뿐이다.** 다만 QEMU RSS는 이미 게스트 할당 상한에
|
||||||
|
근접했으므로 **앞으로 크게 늘지 않는다.** 압박은 호스트가 아니라
|
||||||
|
**게스트 안에서** 생긴다.
|
||||||
|
|
||||||
|
**다음 배포의 예상 소요**
|
||||||
|
|
||||||
|
| 워크로드 | 예상 |
|
||||||
|
|---|---|
|
||||||
|
| Keycloak × 2 | 각 ~800Mi |
|
||||||
|
| PostgreSQL | ~300Mi |
|
||||||
|
| Redis | ~100Mi |
|
||||||
|
| 합계 | 약 2000Mi, 게스트당 ~1000Mi |
|
||||||
|
|
||||||
|
```
|
||||||
|
kc-lab-1 1774 + 1000 ≈ 2774Mi / 3.4GB (80%)
|
||||||
|
kc-lab-2 1032 + 1000 ≈ 2032Mi / 2.4GB (84%)
|
||||||
|
```
|
||||||
|
|
||||||
|
**빠듯하지만 가능하다.** 여유가 필요하면 헤더 실험이 끝난 `header-lab`
|
||||||
|
네임스페이스를 지운다 — 파드 2개 × 150Mi가 회수된다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl delete ns header-lab # 실험 종료 후
|
||||||
|
```
|
||||||
|
|
||||||
|
**모든 워크로드에 `resources.limits`를 반드시 건다.** 안 걸면 한 파드가
|
||||||
|
게스트 메모리를 다 먹고 다른 파드까지 OOMKilled된다. 3.4GB / 2.4GB짜리
|
||||||
|
게스트에서는 현실적인 위험이다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 자주 쓰는 명령
|
||||||
|
|
||||||
|
### VM (lab host, `LIBVIRT_DEFAULT_URI=qemu:///system`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
virsh list --all # 상태
|
||||||
|
virsh domstate kc-lab-1
|
||||||
|
virsh domblklist kc-lab-1 # 붙은 디스크
|
||||||
|
virsh net-dhcp-leases default # 게스트 IP
|
||||||
|
virsh screenshot kc-lab-1 /tmp/kc1.ppm # 화면 (PNG 로 저장됨)
|
||||||
|
virsh send-key kc-lab-1 --codeset linux KEY_ENTER
|
||||||
|
|
||||||
|
virsh destroy kc-lab-1 # 전원 강제 차단 = 노드 상실
|
||||||
|
virsh start kc-lab-1 # 재기동
|
||||||
|
virsh shutdown kc-lab-1 # ACPI 정상 종료
|
||||||
|
```
|
||||||
|
|
||||||
|
**`destroy`는 파일을 지우지 않는다.** 전원 코드를 뽑는 것에 해당한다.
|
||||||
|
정의와 디스크를 지우는 것은 `undefine`이다.
|
||||||
|
|
||||||
|
### 클러스터
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl get nodes -o wide
|
||||||
|
kubectl get pods -A -o wide
|
||||||
|
kubectl -n <ns> logs -f deployment/<name>
|
||||||
|
kubectl -n <ns> describe pod <pod>
|
||||||
|
kubectl -n <ns> rollout status deployment/<name>
|
||||||
|
kubectl -n <ns> rollout restart deployment/<name>
|
||||||
|
kubectl -n <ns> rollout undo deployment/<name> # 직전 버전으로
|
||||||
|
|
||||||
|
# 설정 스위치 껐다 켜기 — 실험의 기본 동작
|
||||||
|
kubectl -n <ns> set env deployment/<name> KEY=VALUE
|
||||||
|
|
||||||
|
# 임시 파드로 클러스터 안에서 테스트
|
||||||
|
kubectl -n <ns> run t --rm -i --restart=Never --image=curlimages/curl:8.11.1 -- \
|
||||||
|
curl -s http://<svc>:<port>/path
|
||||||
|
```
|
||||||
|
|
||||||
|
### 이미지 반입
|
||||||
|
|
||||||
|
k3s는 containerd를 쓰고 레지스트리가 없다. **자체 빌드 이미지는 매번 이
|
||||||
|
경로를 탄다.**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 워크스테이션에서
|
||||||
|
docker build -t keycloak-pattern-api:lab backend
|
||||||
|
docker save keycloak-pattern-api:lab | ssh test-server "ssh kc-lab-1 'sudo k3s ctr images import -'"
|
||||||
|
docker save keycloak-pattern-api:lab | ssh test-server "ssh kc-lab-2 'sudo k3s ctr images import -'"
|
||||||
|
|
||||||
|
# 확인
|
||||||
|
ssh test-server "ssh kc-lab-1 'sudo k3s ctr images ls -q | grep keycloak-pattern'"
|
||||||
|
```
|
||||||
|
|
||||||
|
**두 노드 모두**에 넣는다. 스케줄러가 어디에 배치할지 모른다.
|
||||||
|
매니페스트는 `imagePullPolicy: Never`여야 한다.
|
||||||
|
|
||||||
|
`ctr`이 아니라 **`k3s ctr`** 이다. 시스템에 별도 `ctr`이 있으면 다른 소켓을
|
||||||
|
보게 되어 "성공했는데 파드는 못 찾는" 상태가 된다.
|
||||||
|
|
||||||
|
### 저장소·브랜치
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 워크스테이션 — 작성·커밋
|
||||||
|
git add -A && git commit -m "..." && git push origin develop-keycloak-session-store
|
||||||
|
|
||||||
|
# lab host — 받기만 (읽기 전용으로 운용)
|
||||||
|
cd ~/workspace/keycloak-pattern && git pull
|
||||||
|
|
||||||
|
# 실험별 브랜치 이동
|
||||||
|
git checkout feature/keycloak-multinode-cluster-jdbc-ping
|
||||||
|
```
|
||||||
|
|
||||||
|
**lab host의 저장소는 읽기 전용으로 쓴다.** 거기서 편집하면 드리프트가
|
||||||
|
생긴다 — nginx 설정에서 실제로 겪었다
|
||||||
|
([`two-hop-proxy-header-contract.md`](two-hop-proxy-header-contract.md) 9절).
|
||||||
|
|
||||||
|
### 호스트 nginx
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp deploy/lab/host/nginx-keycloak-lab.conf /etc/nginx/sites-available/keycloak-lab
|
||||||
|
sudo nginx -t && sudo systemctl reload nginx
|
||||||
|
sudo nginx -T | grep -n 'upstream\|server_name' # 최종 병합 설정
|
||||||
|
```
|
||||||
|
|
||||||
|
**`nginx -t`를 통과한 뒤에만 reload한다.** 깨진 설정으로 reload하면 서비스가
|
||||||
|
내려간다. `-T`(대문자)는 include까지 펼친 최종 설정을 출력하므로
|
||||||
|
"파일을 고쳤는데 반영이 안 된다" 상황의 확인 수단이다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 손에 익혀야 할 훈련
|
||||||
|
|
||||||
|
개념은 알지만 직접 해보지 않은 조작들. **남은 실험 5개 중 4개가 훈련 3
|
||||||
|
위에 서 있다.**
|
||||||
|
|
||||||
|
### 훈련 1 — 이미지 반입
|
||||||
|
|
||||||
|
앞으로 BFF·token-mediator를 올릴 때마다 반복된다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t keycloak-pattern-api:lab backend
|
||||||
|
docker save keycloak-pattern-api:lab | ssh test-server "ssh kc-lab-1 'sudo k3s ctr images import -'"
|
||||||
|
docker save keycloak-pattern-api:lab | ssh test-server "ssh kc-lab-2 'sudo k3s ctr images import -'"
|
||||||
|
ssh test-server "ssh kc-lab-1 'sudo k3s ctr images ls -q | grep keycloak-pattern'"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 훈련 2 — 설정 스위치 껐다 켜기
|
||||||
|
|
||||||
|
**설정을 바꿔가며 비교하는 것이 이 실험대의 본체**다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n header-lab set env deployment/echo SERVER_FORWARD_HEADERS_STRATEGY=none
|
||||||
|
kubectl -n header-lab rollout status deployment/echo
|
||||||
|
curl -s https://app1.hyeonworks.com/api/echo | python3 -m json.tool | grep -E '"scheme"|"secure"'
|
||||||
|
# → "http" / false 로 바뀐다
|
||||||
|
|
||||||
|
kubectl -n header-lab set env deployment/echo SERVER_FORWARD_HEADERS_STRATEGY=native
|
||||||
|
kubectl -n header-lab rollout status deployment/echo
|
||||||
|
curl -s https://app1.hyeonworks.com/api/echo | python3 -m json.tool | grep -E '"scheme"|"secure"'
|
||||||
|
# → "https" / true 로 돌아온다
|
||||||
|
```
|
||||||
|
|
||||||
|
### 훈련 3 — 노드를 죽였다 살리기
|
||||||
|
|
||||||
|
**가장 중요하다.** 장애 실험의 전제 조작이다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 죽이기 — 전원 차단에 해당
|
||||||
|
virsh destroy kc-lab-2
|
||||||
|
|
||||||
|
# 관찰 (NotReady 로 바뀌는 데 40초 안팎)
|
||||||
|
kubectl get nodes
|
||||||
|
kubectl -n header-lab get pods -o wide
|
||||||
|
kubectl get events -A --sort-by=.lastTimestamp | tail -20
|
||||||
|
|
||||||
|
# 서비스가 살아있는지
|
||||||
|
curl -sI https://app1.hyeonworks.com/api/echo | head -1
|
||||||
|
|
||||||
|
# 되살리기
|
||||||
|
virsh start kc-lab-2
|
||||||
|
kubectl get nodes # Ready 복귀
|
||||||
|
kubectl -n header-lab get pods -o wide
|
||||||
|
```
|
||||||
|
|
||||||
|
**이번에 특별히 확인할 것** — 현재 **Traefik은 replica 1**이고
|
||||||
|
`kc-lab-2`에 있다. 그 노드를 죽이면 **진입점 자체가 사라지는지**,
|
||||||
|
아니면 다른 노드로 재배치되어 복구되는지 관찰한다.
|
||||||
|
|
||||||
|
| 관찰 | 의미 |
|
||||||
|
|---|---|
|
||||||
|
| `curl`이 계속 200 | svclb가 남은 노드로 흘려보냄 + Traefik 재배치 성공 |
|
||||||
|
| `curl`이 실패했다가 복구 | 재배치에 걸린 시간만큼 다운타임 |
|
||||||
|
| `curl`이 계속 실패 | Traefik replica를 2로 늘려야 한다 |
|
||||||
|
|
||||||
|
이 결과에 따라 **Keycloak 배포 전에 Traefik replica를 조정할지** 결정한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 필요하다면
|
||||||
|
kubectl -n kube-system scale deployment/traefik --replicas=2
|
||||||
|
```
|
||||||
|
|
||||||
|
**망가져도 된다.** `virt-install` 한 줄로 재생성되며
|
||||||
|
([`deploy/lab/README.md`](../deploy/lab/README.md) 게스트 재생성),
|
||||||
|
그러라고 만든 실험대다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 실행 주체 감사 (2026-09-03)
|
||||||
|
|
||||||
|
무엇을 직접 했고 무엇을 대행했는지의 기록. **대행한 항목은 언젠가 직접
|
||||||
|
해야 하는 것들**이다.
|
||||||
|
|
||||||
|
### 직접 수행
|
||||||
|
|
||||||
|
VM 생성 · cloud-init 작성 · SSH 키와 config · k3s server/agent 설치 ·
|
||||||
|
kubeconfig 구성 · 호스트 nginx 설치와 설정 · certbot DNS-01 인증서 발급 ·
|
||||||
|
Cloudflare DNS 레코드 · git 저장소 클론과 브랜치 · `echo.yaml` 최초 배포 ·
|
||||||
|
nginx `X-Forwarded-Proto` 수정
|
||||||
|
|
||||||
|
### 대행 수행
|
||||||
|
|
||||||
|
| 작업 | 언제 다시 필요한가 |
|
||||||
|
|---|---|
|
||||||
|
| 이미지 빌드 → 노드 반입 (2회차 이후) | 자체 이미지를 올릴 때마다 |
|
||||||
|
| `kubectl apply` (traefik HelmChartConfig, NetworkPolicy) | 모든 배포 |
|
||||||
|
| `kubectl set env` / `rollout restart` | 설정 비교 실험마다 |
|
||||||
|
| HelmChartConfig 재조정 대기와 파드 확인 | Traefik 설정 변경 시 |
|
||||||
|
| git 브랜치 생성 · 커밋 · push | 실험마다 |
|
||||||
|
| Playwright 증거 수집 | 브라우저 관점이 필요할 때마다 |
|
||||||
|
| `kubectl run` 임시 파드 위조 테스트 | 클러스터 내부 검증 시 |
|
||||||
|
| 설정 되돌렸다 복구 (단계별 스크린샷) | 비교 증거를 만들 때 |
|
||||||
|
|
||||||
|
### 아직 한 번도 하지 않은 것
|
||||||
|
|
||||||
|
| 항목 | 필요해지는 시점 |
|
||||||
|
|---|---|
|
||||||
|
| **`virsh destroy` + 재생성** | **장애 실험 전부** |
|
||||||
|
| `rebuild-seed.sh` | cloud-init 을 바꿀 때 |
|
||||||
|
| k9s 실사용 | 장애 중 상태 관찰 |
|
||||||
|
| `kubectl delete ns` | 실험 정리, 메모리 회수 |
|
||||||
|
|
||||||
|
### 만들었지만 미검증이었던 것 → 2026-09-03 확인 완료
|
||||||
|
|
||||||
|
| 스크립트 | 결과 |
|
||||||
|
|---|---|
|
||||||
|
| `verify-lab.sh` | 정상 — `lab is healthy` |
|
||||||
|
| `measure-proxy-headers.sh` | 정상 — 4개 항목 모두 출력 |
|
||||||
@@ -232,6 +232,7 @@ ALB 고유 동작, 규모)은 스코프에서 제외한다.
|
|||||||
| 문서 | 내용 |
|
| 문서 | 내용 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [`session-lab-concepts.md`](session-lab-concepts.md) | 등장 개념 전체 (가상화·네트워크·k3s·TLS·패키지) |
|
| [`session-lab-concepts.md`](session-lab-concepts.md) | 등장 개념 전체 (가상화·네트워크·k3s·TLS·패키지) |
|
||||||
|
| [`session-lab-operations.md`](session-lab-operations.md) | 관측 도구 · 자주 쓰는 명령 · 훈련 · 자원 예산 |
|
||||||
| [`two-hop-proxy-header-contract.md`](two-hop-proxy-header-contract.md) | 첫 실험의 측정·진단·수정 |
|
| [`two-hop-proxy-header-contract.md`](two-hop-proxy-header-contract.md) | 첫 실험의 측정·진단·수정 |
|
||||||
| [`deploy/lab/README.md`](../deploy/lab/README.md) | 실험대 구축·복구 절차 |
|
| [`deploy/lab/README.md`](../deploy/lab/README.md) | 실험대 구축·복구 절차 |
|
||||||
| [`four-pattern-tradeoff-matrix.md`](four-pattern-tradeoff-matrix.md) | AP1~AP4 비교. "server session" 행이 B층 대상 |
|
| [`four-pattern-tradeoff-matrix.md`](four-pattern-tradeoff-matrix.md) | AP1~AP4 비교. "server session" 행이 B층 대상 |
|
||||||
|
|||||||
Reference in New Issue
Block a user