2. Nginx 는 X-Forwarded-For 헤더에서 LB IP(b.b.b.b) 를 생략하고 Client 에 Real IP를 찾는데, $remote_addr 은 b.b.b.b 에서 a.a.a.a 로 변경되고 따라서 proxy_set_header X-Real-IP $remote_addr 은 맞는 설정이다. (내가 원하는 설정대로다.)
그런데, Nginx 는 X-Forwarded-For 헤더를 b.b.b.b 대신에 a.a.a.a IP 로 바꾼다.
3. WEBAPP 은 다음과 같은 헤더를 수신한다.
INI
1
2
3
X-Forwarded-For=a.a.a.a,a.a.a.a
X-Real-IP=a.a.a.a
->X-Forwarded-For는a.a.a.a,b.b.b.b여야한다.
내가 필요한 것은 먼저 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_forwarded_for를 설정한 다음 실제 IP를 검색하고 $remote_addr 값을 바꾸는 기능입니다.
이 문제를 어떻게 해결해야 하는지 알려줄 사람?
답1
$proxy_add_x_forwarded_for 는 $http_x_forwarded_for,$remote_addr 와 동일하고 $remote_addr 값은 http_realip_module 을 사용될때에 변경됩니다. 그래서 당신은 그 헤더에서 마지막 Proxy 주소를 얻을 수 없습니다. nginx 설정은 선언적이기 때문에 지시어의 순서 변경은 영향을 주지 않습니다.
http_realip_module 를 사용하면, $realip_remote_addr 값은 (nginx >= 1.9.7) 오리지널 $remote_addr 처럼 사용될 수 있습니다. (역, $realip_remote_addr 은 Nginx 앞에 있는 서버에 IP, 여기서는 LB(b.b.b.b) 를 가리킨다) 따라서 다음과 같이 X-Forwarded-For 를 세팅할 수 있습니다.
나도 같은 문제로 여기까지 왔는데, 이러한 현상은 real_ip_recursive on; 으로 발생된다는 결론을 내렸습니다. Nginx 의 공식문서에 다음과 같이 나옵니다.
If recursive search is enabled, an original client address that matches one of the trusted addresses is replaced by the last non-trusted address sent in the request header field.
이렇게 해서 돌려보면 ip 에는 Nginx 의 ip를 가지고 오게 된다. 이를 해결하기 위한 다양한 방법이 존재한다.
Nginx 설정
Nginx 에서 Real IP 를 가지고 오기 위해서는 Nginx 에 자체 변수인 $remote_addr 에 Client IP 를 넣어줘야 한다. 여기서 ‘$remote_addr 변수에 값을 넣어줘야’ 말이 중요하다.
Nginx 에서도 앞에 수많은 Proxy 가 있을 경우에 $remote_addr 은 이전 Proxy 의 IP 주소가 된다.
Client —-> AWS ALB ——> Nginx
만일 위와같은 구조일 경우에 Nginx 에 $remote_addr 값은 AWS ALB 의 값이 된다.
이 문제를 해결하기 위해서 Nginx 에 ‘–with-http_realip_module’ 을 가지고 있을 경우에 특정 헤더에서 Real Ip 를 찾을 수 있도록 해준다. AWS ALB 의 경우에는 X-Forwarded-For 헤더에 Client IP 를 보존해 준다. 따라서 Nginx 에서는 다음과 같이 설정해줌으로써 $remote_addr 에 Client IP를 넣을 수 있다.
realip config
Apache
1
2
3
4
set_real_ip_from172.10.10.0/24;# ALB A zone
set_real_ip_from172.10.20.0/24;# ALB C zone
real_ip_headerX-Forwarded-For;
real_ip_recursiveon;
X-Forwarded-For 라는 헤더에서 Client IP 를 찾아야 하는데, set_real_ip_from 에 있는 IP 는 그냥 신뢰할 수 있는 IP 로 정의하고 Client IP 에서 제외된다. 이렇게 하는 이유는 X-Forwarded-For 가 다음과 같은 구조를 가지기 때문이다.
X-Forwarded-For: 213.13.24.10, 172.10.10.34
신뢰할 수 있는 IP 라는 건 Proxied IP 를 말하는 것으로 중간에 있는 서버들을 말한다. 이러한 신뢰할 수 있는 IP 가 X-Forwarded-For 에 있기 때문에 이것은 Client IP가 될 수 없다. 이 신뢰할 수 있는 IP 를 제외하고 나면 Client IP 만 남게 되고 이것을 $remote_addr 변수에 담게 된다.
이렇게하면 끝났으면 좋을련만, Nginx 뒤에 있는 Spring Boot3 에 req.getRemoteAdd() 함수에는 여전히 Client IP가 잡히지 않는다. Nginx 에 $remote_addr 은 어디까지나 Nginx 내에서만 활용되는 것이고 뒷단에 연결된 서버까지 연향을 주지 않는다.
이 경우에는 별도의 Header 를 정의해서 그 Header 값을 가지고 옴으로써 Client IP를 얻을 수 있다. 먼저 Nginx 에서 Header 값을 지정해줘야 한다. 다음과 같이 proxy_set_header 를 이용해 X-Real-IP 에 $remote_addr 값을 담아 준다.
Nginx 에서 설정을 하게 되면 결국 Spring Boot3 에서 Nginx 에서 넘겨주는 Client IP 를 담고 있는 별도의 Header 값을 가지고 와야 하는 코드를 작성해야 한다.
Spring Boot3 에 Tomcat 설정
원래 Tomcat 에서는 Valve 를 이용해서 Real IP 를 가지고 올 수 있다. 놀랍게도 이 방법은 Nginx 의 설정과 방법과 동일하고 문법만 다르다. 신뢰할 수 있는 IP 대역을 지정하고 체크할 Header 를 지정해 주면 된다. 이것을 Spring Boot3 에서는 application.properties 에 설정만으로 간단하게 할 수 있다.
최근들어 arm64 아키텍쳐가 인기가 많아졌다. 애플의 실리콘 반도체라고 불리는것도 arm64 기반이며 Windows 11 도 arm64 에서도 작동된다. 리눅스는 오래전부터 다양한 아키텍쳐로 포팅이되었기 때문에 arm64 를 위한 배포판도 다양하게 존재한다. 문제는 arm64 아키텍쳐를 경험하기 위해서 arm64 하드웨어가 있어야 했지만, 이제는 x86_64 기반의 가상머신을 이용하면 arm64 아키텍쳐를 게스트로 운영할 수 있다.
이 문서는 x86_64 기반 가상머신에서 arm64 아키텍쳐기반의 게스트를 실행하는 방법에 대해서 기술한 것이다.
x86_64 가상머신
x86_64 아키텍쳐 기반의 가상머신으로 리눅스 운영체제를 기반으로 KVM 을 활용하고 있다. GUI 툴로서 virt-manager, CLI 로는 virsh 를 활용해서 간단하게 게스트를 생성하고 운영하고 있다. arm64 아키텍쳐 게스트를 운영하기 위한 호스트로서 x86_64 아키텍쳐 기반 가상머신 스펙은 다음과 같다.
OS: Ubuntu 22.04
Kernel: 5.15.0-207.156.6.el9uek.x86_64
CPU: AMD Ryzen 7 2700X Eight-Core Processor
libvirt vs QEMU
arm64 아키텍쳐기반 게스트를 운영하기 위해서는 QEMU 를 사용해야 한다. QEMU 는 가상머신 에뮬레이터라고 생각하면 된다. 문제는 QEMU 는 CLI 기반만 제공한다.
반면에 libvirt 는 KVM/QEMU 등을 지원하는 일종의 핸들러 라이브러리고 생각하면 된다. libvirt 를 이용하면 xml 기반으로 게스트 관련 가상머신 스펙을 정의할 수 있으며 virt-manager 와같은 GUI 툴도 활용할 수 있다.
QEMU 에뮬레이터라고 했기 때문에 arm64 를 위한 QEMU 에뮬레이터를 설치하면 arm64 기반 게스트 운영체제를 운영할 수 있다. 다음과 같이 arm64 를 위한 QEMU 에뮬레이터를 설치해준다.
qemu-system-arm 설치
ZSH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$sudo apt install qemu-system-arm
Reading packagelists...Done
Building dependency tree...Done
Reading state information...Done
The following packages were automatically installed andare no longer required:
처음부터 Arm64 기반 OS 를 게스트로 설치하면서 생성할 수도 있다. 하지만 이미 있는 Arm64 기반 OS를 가져다 쓸 수도 있다. 여기서는 Amazon Linux 를 가져다 사용해 보도록 하겠다.
Amazon Linux 는 Amazon 이 AWS 서비스에서 사용할 목적으로 만든 배포판이다. 현재 Amazon Linux2 와 Amazon Linux3 등 다양한 버전을 제공한다. 최근 프로젝트가 Amazon Linux2 를 많이 사용하고 있어서 Amazon Linux2 Arm64 기반 OS 이미지를 게스트로 한번 실행 보도록 하겠다.
Amazon Linux2 는 기본적으로 ec2-user 라는 시스템 계정이 있으며, 로그인을 위해 패스워드 방식이 아닌 SSH 인증키 방식을 사용한다. 하지만 배포되는 이미지에 맞는 인증키가 따로 없기 때문에 부팅과정에서 이 부분을 변경하도록 해야하는데, 이를 위해서 seed.iso 를 만들어 게스트 OS 에 CD-ROM 에 넣어 부팅해준다. 이 부분에 대한 설명은 다음의 페이지에서 찾을 수 있다.
systemctl 은 리눅스에 서비스 데몬을 설정하는 것과 유사하다. 과거 init script 를 systemd 로 전환하면서 만들어진 것인데, 문제는 시스템의 서비스 데몬 등록이라서 대부분 root 권한으로 실행된다. 만일 일반 사용자가 systemd 유닛으로 등록하기 위해서는 어떻게 해야 할까…
~/.config/systemd/user
사용자 홈디렉토리에 ~/.config/systemd/user 디렉토리를 생성한다. 이 디렉토리에 사용자 systemd 유닛 파일을 작성해야 한다.
test.service
systemd 유닛 파일의 확장자는 service 다. 그리고 반드시 다음과 같이 WantedBy 값을 default.target 으로 해줘야 한다. 가끔 multi-user.target 으로 하는 경우가 있는데, 이걸로 할 경우 부팅시에 자동으로 실행되지 않는다.
test.service
INI
1
2
[Install]
WantedBy=default.target
서비스 활성화
이제 다음과 같이 사용자 서비스를 활성화 해준다. 이렇게 함으로써 부팅시에 자동으로 서비스가 시작된다.
서비스 활성화
ZSH
1
]$systemctl--user enable test.service
이렇게 세팅을 하게되면 리눅스 서버가 부팅될때마다 사용자 서비스도 함께 자동으로 실행이 된다.
루트(root) 사용자 systemctl –user 사용하기
systemctl –user 는 일반사용자가 사용하는 명령어다. 하지만, root 사용자가 systemctl –user 명령어를 사용할 수 있을까? systemd 248 (released March 2021) 버전에서 -M username@ 옵션을 통해서 root 사용자가 명령어를 실행할 수 있다.
systemctl --user
ZSH
1
]# systemctl --user -M user1@ status myunit.service
민트 리눅스 21.03 에서 KVM 가상환경을 구성해 본다. 구성에 핵심은 KVM 의 네트워크 설정이다. 앞서 설정한 OpenvSwitch 를 이용하도록 설정 해야 한다.
네트워크 환경
KVM 가상화를 위해서는 네트워크 환경을 먼저 고려해야 한다. 필자의 경우에는 공유기를 이용하고 있다. 외부 광랜으로 들어온 라인을 모뎀에서 받아서 이더넷로 변환해준다. 여기서 랜선으로 공유기에 연결하고 각 컴퓨터에 연결해서 쓴다.
공유기는 다들 아는 IpTime 인데, IpTime 은 새로운 장비가 접속되면 자동으로 사설IP 를 할당해 준다. 이것을 외부로 내보낼때는 NAT 기능을 이용해서 하나의 인터넷 라인으로 공유기 안쪽에 많은 장비를 사용할 수 있게된다.
KVM 가상화를 하게되면 브릿지 네트워크가 생성된다. 이 브릿지 네트워크는 NAT 모드로 작동된다. 172.x.x.x 대역으로 게스트에게 자동으로 IP 를 할당해 준다. 마치 IpTime 공유기와 같은데, 문제는 이렇게 되면 다른 컴퓨터에서 게스트에 접속할 수가 없게 된다. NAT 는 단반향으로 게시트에서 바깥으로 접속은 할 수 있지만 바깥에서 게스트로 접속은 불가능하다. 가능한 방법은 Port 포워딩인데, 포트마다 설정을 해줘야 하는 번거로움이 있다.
KVM 네트워크 설정을 NAT 가 아닌 브릿지(Bridge) 모드로 설정하고 드라이버를 OpenvSwitch 로 설정하면 호스트 컴퓨터에 브릿지 네트워크인 OpenvSwitch 를 통해서 IpTime 에서 사설 IP를 받게 된다. IpTime 내에 네트워크 모두 접속이 가능해 진다.
민트 리눅스(Mint Linux) 21.03 을 쓰고 있는데, 네트워킹이 NetworkManager 으로 되어 있다. Ubuntu 22.04 LTS 에서는 networkd 였지만 민트 리눅스는 같은 Ubuntu 라고 하더라도 네트워킹 운영을 NetworkManager 가 담당하고 있다.
이 문서는 민트 리눅스에서 OpenvSwitch 설정에 대한 것이다. Ubuntu 와 다른 네트워킹을 사용하기 때문에 nmcli 명령어를 이용한 방법을 소개 한다.
민트 리눅스 네트워킹 설정
이렇게 NetworkManager 일 경우에는 네트워크 인터페이스 관련 설정을 nmcli 로 하게된다. 물론 민트 리눅스 이기 때문에 GUI 를 통해서 손쉽게 할 수 있다.
GUI 툴을 이용해서 이렇게 설정을 하게 되면, nmcli 명령어를 사용해서 하는 것과 동일하게 nmcli 관련 설정파일이 변경 된다. 다음과 같은 경로에 파일이 존재한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
]# ls /etc/NetworkManager/system-connections/
'유선 연결 1.nmconnection'
]# cat 유선\ 연결\ 1.nmconnection
[connection]
id=유선연결1
uuid=f2de78ab-770b-3d94-9e17-12345
type=ethernet
autoconnect-priority=-999
interface-name=enp5s0
timestamp=1708005693
[ethernet]
wake-on-lan=64
[ipv4]
address1=192.168.96.4/20,192.168.96.1
dns=192.168.96.7;8.8.8.8;
method=manual
[ipv6]
addr-gen-mode=stable-privacy
method=auto
[proxy]
GUI 툴을 이용해 설정한 내용이 위 파일에 적용된다.
OpenvSwitch 설치
OpenvSwitch 를 설치를 먼저 해야 한다. Ubuntu 를 사용하고 NetworkManager 일 경우에 OpenvSwitch 도 NetworkManager 와 연관된 패키지를 설치하는 경우가 많지만 민트 리눅스에는 다음과 같은 패키지를 설치 한다.
2월1523:34:36systemv-desktop systemd[1]:Starting Open vSwitch...
2월1523:34:36systemv-desktop systemd[1]:Finished Open vSwitch.
systemd 에 설정이 되었고 시작도 되었다. 다음과 같이 명령어가 문제없이 출력되는 확인한다.
1
2
3
]# vs-vsctl show
c9b08240-08fb-438b-a314-123456
ovs_version:"2.17.8"
위와같이 정상적으로 버전이 출력되어야 한다.
network-manager 패키지 문제
민트 리눅스 21.03 에서는 network-manager 패키지에 문제가 있다. OpenvSwitch 플러그인이 비활성화된 패키지라는 거다. 다음과 같다.
1
2
--disable-modify-system\
--disable-ovs
ovs 를 활성화하기 위해서는 network-manager 를 재패키징 해야 한다. 이를 위해서는 소스 deb 를 다운로드 받아야 한다.
1
]# apt source network-manager
이렇게하면 디렉토로에 network-manager 관련 패키징을 위한 파일과 디렉토리가 생성된다. 여기서 다름과 같이 deb 패키징을 위한 configure 파일이라 할 수 있는 rules 파일을 수정해 줘야 한다.
1
2
]# cd network-manager-1.36.6/debian
]# vim rules
rules 파일을 열면 configure 설정을 볼 수 있다. 여기서 –disable-ovs 를 삭제한다. 이렇게 되면 ovs 를 위한 설정파일이 생성되고 이 파일에 대한 처리가 없으면 deb 패키징이 실패한다. 설치를 위한 파일 목록은 network-manager.install 파일이다. 여기서 다음을 추가해 준다.
만약 이렇게 자동으로 디스크가 인식되지 않는다면 SCSI 디스크 ReScan 기능을 이용해야 한다. Hot Swap 은 SCSI 의 host 번호를 이용해 작동됨으로 SCSI host 번호를 알아야 한다. 하지만 리눅스에서는 /dev/sdc 형식인데 SCSI host 번호와는 다른데, 다음과 같이 알아내야 한다.