[WordPress] Cloudflare + Kubernetes 환경에서 Real IP 얻기

Preface

최근들어 부쩍 스팸성 댓글이 활발한것 같다. WordPress에서 글이나 댓글이 작성되면, 스팸 필터링 등을 위해 작성자 IP등의 정보가 기록된다. 스팸으로 의심되는 IP나 댓글 본문은 자동으로 스팸 필터링이 적용되는데, 이 때 구축 환경 때문에 실제 IP (Real IP)가 기록되지 않는 문제가 종종 있는것 같다.

댓글이 작성되면 실제 IP가 아닌 내부 IP가 기록된다.

지금 구축된 환경에서는 사실 실제 IP(Real IP)가 직접 전달되기는 어렵다. 이를 해결하려면 끝점에 있는 WordPress에서 HTTP Header를 파싱하여 실제 IP를 가져올 필요가 있다.

Experiment #1. Cloudflare without Proxy

Cloudflare Proxy를 끄고 HTTP Request와 Header가 변형되는 과정. 물론 환경마다 다를수 있다.
직접 testbed.dailylime.dev로 테스트해볼 수 있다.

Experiment #2. Cloudflare with Proxy

Cloudflare Proxy를 켠 상태로 HTTP Request와 Header가 변형되는 과정. 물론 환경마다 다를수 있다.
직접 testbed.dailylime.dev로 테스트해볼 수 있다.

Forwarded Headers

이를 해결하기 위해서는 Header로 포워딩된 Original IP를 파싱할 필요가 있다. Cloudflare는 Origin IP를 CF-Connecting-IPX-Original-Forwarded-For 두 가지로 Forward한다. 여기서는 CF-Connecting-IP를 가지고 Origin IP를 파싱하는 방법을 사용한다.

WordPress에서의 세팅 방법은 간단하다. wp-config.php에 아래와 같이 세팅한다.

/**
 * Fixing original IP detection behind Nginx proxy
 */
if(isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
    $list = explode(',',$_SERVER['HTTP_CF_CONNECTING_IP']);
    $_SERVER['REMOTE_ADDR'] = $list[0];
}

Conclusion

결국 wp-config.php에 몇 줄만 추가되면 되는 내용인데. 사실상 정확히 알고자 하면 복잡한 부분이다. 현재 구축되어 있는 Reverse proxy에 대한 구조에 대한 이해 등이 일부 필요한 부분이 있다. 실제로 Reference [1]에서는 X-Forwarded-For를 사용하는 방법을 명시하고 있는데, Kubernetes Ingress 뒤에 있는 경우 이 값도 사실상 Internal IP이거나 Private IP일 확률이 높다. 그렇기 때문에, 정확하게 어떤 Header가 전달되는지를 확인할 필요가 있다.

아래 jungin500/http-request-tester 레포지토리는 필요한 경우 테스트 목적으로 활용 가능하다.

  • jungin500/http-request-tester:python
  • jungin500/http-request-tester:cplusplus

처음에는 Python 구현체로 만들고, 다시 C++로 변환하였다. Claude 만만세

Reference

[1] Technicus, Configure WordPress to report true IP addresses when behind a reverse proxy: https://techblog.jeppson.org/2014/09/configure-wordpress-to-report-true-ip-addresses-when-behind-a-reverse-proxy/

[2] Cloudflare Docs, Restoring original visitor IPs: https://developers.cloudflare.com/support/troubleshooting/restoring-visitor-ips/restoring-original-visitor-ips/#nginx-1

[3] Cloudflare, IP Ranges: https://www.cloudflare.com/ko-kr/ips/

[Kubernetes] PyTorch 학습 시 Pod의 빠른 종료를 위한 yaml 설계 방법

[새로운 방법 발견!]

아래 방법을 계속 사용하다가, yaml에 직접 넣어서 사용할 수 있는 구문을 찾았습니다.

spec:
  terminationGracePeriodSeconds: 0

해당 구문을 이용하여 Pod delete시 바로 중단 및 삭제되도록 할 수 있습니다.


기존 방법으로는 kubectl delete 시 --force --grace-period=0 옵션을 주어야만 강제 종료되고, 학습 process에 실제로 Ctrl+C를 주는 것과 동일하게 SIGTERM 을 보낼 수는 없었습니다. 이러한 문제점은 학습 프로세스의 종료시 callback들 (wandb 등)이 정상 작동하지 않는다는 문제점이 있습니다.이를 해결할 수 있는 방안을 소개 드립니다.
다만 아래와 같은 몇 가지 drawback이 있으므로 참고하여주시고, 더 좋은 방법이 있다면 공유 부탁드립니다.

  1. pip install 이 불가능하므로 미리 requirements.txt를 설치한 image를 준비해야 함
  2. runAsUserrunAsGroup 을 1003으로 고정해야 하므로 HOME 변수를 /workspace로 고정해야만 사용 가능

기존 방식

apiVersion: v1
kind: Pod
metadata:
  name: jungin500-mobilenetv2
spec:
  securityContext:
    runAsUser: 0
    runAsGroup: 0
    fsGroup: 1003

  restartPolicy: Never

  volumes:
    - name: shmdir
      emptyDir:
        medium: Memory
    - name: pvc-volume
      persistentVolumeClaim:
        claimName: lab-pvc

  containers:
    - name: gpu-container
      image: ghcr.io/jungin500/mobilenet_v2
      volumeMounts:
        - mountPath: /dev/shm
          name: shmdir
        - mountPath: /home/lab
          name: pvc-volume
      env:
        - name: TZ
          value: Asia/Seoul
      command:
        - "/bin/sh"
        - "-c"
      args:
        - >-
          set -x &&
          groupadd -g 1003 lab &&
          useradd -m -d /workspace -s /bin/bash -u 1003 -g lab lab &&
          runuser -u lab -- git clone https://jungin500:*****@github.com/jungin500/mobilenet_v2 mobilenet_v2 &&
          cd /workspace/mobilenet_v2 &&
          runuser -u lab -- git checkout 7d198633d19c2005e22b118ba27082eca3f2846a &&
          runuser -u lab -- pip3 install -r requirements.txt &&
          runuser -u lab -- /bin/bash -c "mkdir -p /home/lab/jungin500/mobilenet_v2_220718 || true" &&
          runuser -u lab -- python3 train.py ****
      securityContext:
        allowPrivilegeEscalation: false

      resources:
        requests:
          nvidia.com/gpu: 1
        limits:
          nvidia.com/gpu: 1

변경된 방식

apiVersion: v1
kind: Pod
metadata:
  name: jungin500-shutdown-signal-test
spec:
  securityContext:
    # [1] runAsUser, runAsGroup을 Fix
    runAsUser: 1003
    runAsGroup: 1003
    fsGroup: 1003

  restartPolicy: Never

  volumes:
    - name: shmdir
      emptyDir:
        medium: Memory
    - name: workdir
      emptyDir: {}
    - name: pvc-volume
      persistentVolumeClaim:
        claimName: lab-pvc

  # [2] git clone, 학습 결과물 폴더 링크 세팅등을 initContainers에서 진행
  # 이 때, pip install 등은 불가능하므로 미리 requirements.txt를 설치한 image를 준비해야 함
  initContainers:
    - name: clone-directory-set
      image: alpine/git
      volumeMounts:
        - mountPath: /workspace
          name: workdir
        - mountPath: /home/lab
          name: pvc-volume
      workingDir: /workspace
      env:
        - name: TZ
          value: Asia/Seoul
        - name: HOME
          value: /workspace
      command:
        - "/bin/sh"
        - "-c"
      args:
        - >-
          set -x &&
          git clone https://jungin500:***@github.com/jungin500/mobilenet_v2 mobilenet_v2 &&
          cd /workspace/mobilenet_v2 &&
          git checkout 7d198633d19c2005e22b118ba27082eca3f2846a &&
          mkdir -p /home/lab/jungin500/mobilenet_v2_test || true
  
  # [3] 기타 작업 (폴더 생성, 이동 등)은 불가능함
  #     argument도 string의 형태가 아닌 array로 주어야 작동함
  containers:
    - name: gpu-container
      image: ghcr.io/jungin500/mobilenet_v2
      volumeMounts:
        - mountPath: /dev/shm
          name: shmdir
        - mountPath: /workspace
          name: workdir
        - mountPath: /home/lab
          name: pvc-volume
      workingDir: /workspace/mobilenet_v2
      env:
        - name: TZ
          value: Asia/Seoul
        - name: HOME
          value: /workspace
      command:
        - "/opt/conda/bin/python3.8"
        - "train.py"
      args:
        - "--arg1"
        - "arg1_value"
      securityContext:
        allowPrivilegeEscalation: false

      resources:
        requests:
          nvidia.com/gpu: 1
        limits:
          nvidia.com/gpu: 1

구체적으로 변경되어야 하는 부분은 다음과 같습니다.

spec.securityContext.runAsUser
spec.securityContext.runAsGroup
spec.volumes.name[workdir]
spec.initContainers
spec.containers.volumeMounts[mountPath=/workspace]
spec.containers.env[name=HOME]
spec.containers.command
spec.containers.args