GenAI and LLM

AI 를 배울려면 어려운것이 용어다. 알수 없는 용어들. 기존 IT는 그래도 여기저기 주워들은(?) 게 있어서 한번 들으면 그런대로 잘 이해가 되는데, AI 분야는 한번 들었다고 해서 그게 바로 머리속에 자리잡는건 아닌듯 했다. 그래서 용어를 정리해야할 필요가 있다.

GenAI

Generate AI 를 줄여서 쓰는 말이다. 흔히 AI 라고하면 GenAI 를 말하는 것으로 착각하지만 불과 몇년전만 하더라도 AI 는 Specialized AI 였다.

GenAI는 텍스트(Text), 이미지(Images), 오디오(Audio), 비디오(Video)와 같은 새로운 콘텐츠를 생성할 수 있는 시스템을 의미한다. Specialized AI 는 특정 태스크에 특화된 AI를 말하는데, 이미지를 조정하거나 음성인식등인데 사람이 생성한 컨텐츠와 구별되는 특정한 태스크를 위해 디자인 된 것을 말한다. 반면에 GenAI 는 사람이 생성한건지, AI 가 생성한건지 구별하기가 매우 어렵다.

GenAI 는 어마어마한 데이터를 학습한 neural networks (NHs) 와 같은 machine learning(ML) 를 사용한다. 학습하는 방법은 훈련 데이터(Training Data) 를 가지고 패턴(Pattern)과 구조(Structures) 를 학습하고 이를 기반으로 데이터의 확률 분포를 모델링하고 이를 기초로해서 새로운 패턴과 구조를 다시 만들어낸다.

여기서 중요한것이 Training Data 를 가지고 패턴(Pattern)과 구조(Structures) 를 파악하는게 아니라 학습한다는데 있다. 이 학습을 위해서 방대한 데이터가 필요하고 이 방대한 데이터를 정제해서 훈련 데이터(Training Data) 를 다시 만들어야 한다.

따라서 데이터를 정제시키는 방법이 필요한데, Python Pandas 가 시작점이 될 수 있다. Pandas 를 데이터를 DataFrame 으로 전환시켜서 각종 연산을 통해서 훈련 데이터를 생성해준다.

LLM

Large Language Model. 한국어로는 대형 언어 모델이라고 한다. 여기서 용어에 의미를 명확히 해야 한다. 대형(Large) 와 모델(Model) 이라는 말이 함께 쓰이기 때문에 모델 자체에 어떤 대형이 있을 거라 생각하겠지만 여기서 대형(Large)는 훈련 데이터(Training Data) 가 아주 크다는 것을 말한다.

AI 는 모델을 만들기 위해서는 훈련 데이터, 그전에 정제되지 않은 Raw Data 가 필요한다. LLM 은 이 쌩짜(Raw Data) 와 훈련 데이터가 어마어마하게 크다는 것을 의미 한다.

GenAI 에서 가장 발전이 많하는 분야이고 자주 접하는 분야가 LLM 인데, 최종적으로 LLM 을 통해서 자연어 생성(NLG, Natural language generation)이 목적이다. LLM 은 GenAI 에서 인간의 언어를 이해고 인간의 언어를 생성하도록 특화된 신경망(neural networks) ML 이라고 볼 수 있다. GenAI 는 그야말로 다양한 능력을 가지는 AI 인데, LLM 은 그중에서 인간의 언어에 초점이 맞춰진 것이라고 할 수 있다.

인간의 언어를 이해한다….. 어찌보면 인간이 어렸을적에 언어를 배우는 방법과 비슷한데, 수많은 단어를 학습하고 언어 구조를 파악하고 종합해 뜻을 이해하고 단어와 구조를 이용해 다시 말을 하게 된다.

따라서 LLM 도 어마어마한 인간의 단어를 학습해야 한다. 그리고 그 언어의 구조도 학습해야 한다. 인간의 언어를 학습한 모델은 그 자체도 어마어마하게 클 것으로 예상되는데, 그런면에서 대형(Large) 라는 말은 훈련 데이터의 크기뿐만 아니라 그것을 학습한 AI 모델도 매우 큰 규모라는 것을 뜻하기도 한다. 또, 당연히 LLM 은 학습 데이터가 많아지면 많아질 수록 더 좋은 성능을 나타낼 것은 당연하다.

여기서 LLM 의 성능이라고 하면, 좀 더 인간에 가까운 텍스트 컨텐츠라고 할 것이다. 이를 위해서 더 많은 단어와 구조를 학습해야 한다. 더 많이 학습하면 할수록 인간과 유사한 자연스러운 텍스트 컨텐츠를 생성하게 된다. 결국에는 인간과 구별되지 않는 창조적인 대규모 자연어 콘텐츠를 자동으로 생성하는게 최종 목표라 할만 하다.

Deep Learning

딥러닝(Deep Learning)은 머신러닝(Machine Learning)의 한 분야로, 인간의 뇌 구조를 모방한 인공 신경망(Neural Networks)을 이용하여 복잡한 패턴을 학습하는 방식. 머신러닝은 다양한 알고리즘을 통해 데이터를 분석하고 예측 모델을 만드는 것을 의미하며, 딥러닝은 이러한 머신러닝 알고리즘 중에서도 특히 인공 신경망을 활용한 심층 학습을 강조한다.

딥러닝은 한걸음 더 나가서 스스로 학습한다는 것이다. 인공 신경망이기 때문에 가능 이야기다.

Transformer

어렵다… 이건 어려운 주제다. 쉽게 이해가 가지 않는 것이, 논문수준의 이론적 이해를 필요하기 때문이다. 기존의 이론에서 Attention Machanism 만 가지고 언어 추론을 한다고 하는데…

뭔 소리여?

일단 넘어가고 나중에 이해해 보자. 지금은 몰라도 된다.

Iterm2 설정

처음 Mac 을 구매하게 되면 제일 먼저 하는 것이 Iterm2 를 설치하는 것이다. Mac 에서 제공하는 Term 보다 사용하기 편해서 기본적으로 다 설치를 하게 된다. Iterm2 는 테마, 폰트등을 설정할 수 있고 Git 을 이용할 경우에 터미널에서 Branch 상태등을 잘 보여줘서 거의 필수로 설치를 하게된다.

기본상태

Iterm2 를 설치하면 다음과 같다.

프롬프트나 이런것들이 기본상태이다.

oh-my-zsh 설치

oh-my-zsh 는 zsh 에 많은 기능을 부여해준다. 대표적인 것이 Themes, Fonts 등이다. 다음과 같이 설치할 수 있다.

설치를 위와같이 쉘 프롬프트가 변경 된다.

.zshrc

zsh 의 설정은 zshrc 파일에 담겨 있다. 여기에 보면 oh-my-zsh 를 활성화하도록 되어 있으며, 테마등을 설정할 수 있다.

powerlevel10k 테마

여러가지 테마가 있는데, powerlevel10k 테마가 제일 좋아 보인다. 사람마다 성향이 다르기 때문에 어느게 좋다할 수 없지만, 찾아보면 여러가지 테마가 있으니 이것저것 설치하고 변경해보고 하면서 자신에게 맞는 테마를 고르면 된다.

powerlevel10k 는 문답식으로 세팅을 진행하게 된다.

이제, .zshrc 에 ZSH_THEME 를 값을 “powerlevel10k/powerlevel10k” 로 지정한다.

문답설정

zshrc 에 테마를 설정하고 새로운탭으로 터미널을 열면 문답이 진행이 된다.

모든 설정을 하고 나면 위와같이 안내문과 함께 종료 된다.

만일 설정을 다시하고 싶다면 다음과 같이 하면 된다.

Azure Application Gateway 특징

Azure Application Gateway 에 특징이 있는데, 이에 대해 정리한다.

서브넷(Subnet)

Application Gateway 를 위한 전용의 서브넷이 있어야 한다. 서브넷에 다른 서비스가 있다면 선택이 되지 않는다. 예를들어 Subnet1 에 VM 을 생성했다고 하면 Application Gateway 서브넷으로 사용할 수 없다.

따라서 네트워크 설계를 할때에는 Application Gateway 를 위한 전용의 서브넷을 미리 생성을 해야 한다.

Private Subnet 도 가능

Application Geteway 를 생성할때에 Subnet 을 지정하게 된다.

위와같이 Private Subnet 을 지정할 수 있다. 이렇게 지정하고 난후에도 Application Gateway 에 동작하는데에는 아무런 문제가 되지 않는다.

Azure Subnet 에서는 Private Subnet 을 지정하는 옵션이 존재한다. 다음과 같이 설명되어 있다.

Private subnets enhance security by not providing default outbound access. To enable outbound connectivity for virtual machines to access the internet, it is necessary to explicitly grant outbound access. A NAT gateway is the recommended way to provide outbound connectivity for virtual machines in the subnet. 

OutBound Access 에 제한이 걸릴 뿐 Application Gateway 의 동작에는 아무런 문제가 되지 않는다.

Azure Load Balancer

Azure 를 하면서 여러가지 차이점을 느끼지만, 그 중 Azure Load Balancer 만큼 차이를 보여주는 것도 없어보인다. AWS 에서는 Network Load Balancer 에 대응되지만 기능이 몇가지 더 있음으로 해서 아키텍쳐에도 차이를 보인다.

SNAT

Azure LoadBalancer 는 SNAT 기능을 지원한다. Source NAT 라고 하는 것으로 내부 사설IP 를 외부 공인IP 로 변경해 주는 기능이다. 이 기능은 내부 사설망에서 외부 공인 인터넷망으로 통신을 가능하게 해주는 중요한 기능이다.

VM 기반 리소스를 생성할때에 보안상 대부분 사설 서브넷(Private Subnet) 에 위치시킨다. 그리고 서비스를 위해서 VM 앞단에 Load Balancer 를 위치킨다. 문제는 VM 자체적으로 인터넷 통신을 하려고할 때다. 공인IP 가 있다고 하더라도 사설 서브넷에 위치된 VM 은 라우팅으로 인해서 외부로 나갈 수 없어 인터넷 통신을 할 수 없다. AWS 의 경우 이 문제를 해결 하기 위해서 NAT Gateway 서비스를 이용해야 한다.

Azure 에서는 Load Balancer 를 이용하면 이러한 문제를 해결 할 수 있다. 물론 Azure 에서도 NAT Gateway 서비스가 존재하지만 Azure 아케텍쳐상 베스트 프렉티스(Best Practice) 가 아닌 것으로 보인다.

Azure VM 기준 아케텍쳐인데(Azure 공인문서), Frontend 와 Backend 에 위치한 VM들은 모두 사설 서브넷이며 인터넷 통신을 위해서 Load Balancer 를 이용하고 있다. NAT Gateway 를 이용하면 더 좋아 보이는데, 베스트 프렉티스로 되어 있지 않은 모양이다.

Load Balancer 생성

Azure 에 Load Balancer 는 L4 Layer 다. AWS 에 Network Load Balancer 에 대응되지만 SNAT 기능을 제공함으로써 인터넷 통신을 가능하게 해준다.

Load Balancer 생성화면에서 많은 절차가 필요하다는 것을 알수 있는데, Outbound rules 이 SNAT 기능을 사용하는 것이다. 물론 이 기능을 사용하기 위해서는 Inbound rules 설정에서 SNAT 기능을 사용하겠다고 활성화를 해줘야만 한다.

Port allocation

SNAT 설정할때에 문제가 되는 항목이 바로 Port allocation 이다. SNAT 자체가 VM 에서 출발하는 IP 를 변환해주는 것인데, 어쨋거나 Load Balancer 가 대신 공인 인터넷으로 접속을 하게 해주는 것임으로 결국에는 Load Balancer 가 출발지로 되고 결국에는 Client Port 가 있어야 한다.

문제는 VM 에서 인터넷 접속을 이것저것 하게 되면 단 하나의 Client Port 가지고는 되지 않는다. 브라우져를 통해 웹사이트 접속을 하더라도 동시에 몇십게의 Client Port 가 생성된다. 따라서 Load Balancer 입장에서는 뒷단에 있는 VM 들에게 무제한 접속을 허용, 다시 말해서 Client Port 를 할당해 줄수가 없게 된다.

이를 위해서 Azure 는 Port allocation 설정을 하도록 하고 있다. Load Balancer 의 Frontend IP 당 뒷단 VM 수를 조합해 VM 하나에 할당가능한 Port 의 개수를 정하도록 해 놨다.

Port allocation 의 방식을 Manually 로 하지 않고, Azure 에 위임할 경우에 Scale out 시에 뒷단 VM 의 연결이 안될 수도 있다. 위와같이 수동으로 하고 뒷단 인스턴스 개수를 지정해주는 것을 권장하고 있다.

이 설정을 하게되면 SNAT 설정이 완료된다. 이제 VM 에서 인터넷이 되는지 확인해보면 된다.

DNAT

Azure Load Balancer 는 DNAT 기능을 제공한다. 이 기능일 이용하면 Load Balancer 뒷단 VM 에 접속을 할 수 있다. 하지만 이 기능은 잘 사용하지 않는 것으로 보인다. 베스트 프렉티스 아키텍쳐에서는 Bastion 서버를 구성해서 사용하는 것으로 보이는데, 어쨌든 DNAT 기능을 제공한다.

SNAT 기능은 Outbound rule 이라고 한다면 DNAT 는 Inbound NAT rule 이라고 한다.

테스트를 위해서 SSH(22 port) 를 활성해서 뒷단 VM 에 접속하도록 했다. 이렇게 되면 Load Balancer 의 공인IP 를 기반으로 VM 에 22 포트로 접속이 가능해 진다. Frontend Port 의 경우에는 잘 알려진 포트가 아닌 다른 포트를 이용하는 것이 좋다.

결론

Azure Load Balancer 는 L4 Layer 장비다. AWS 와 달리 SNAT, DNAT 기능을 모두 지원함으로써 뒷단 VM 에 특정한 기능을 제공한다.

Azure 기술 문서에는 사설 서브넷에 위치한 VM 의 인터넷 연결을 위해서 Load Balancer 를 이용하고 있다. VM 접속을 위해서는 Bastion 서비스를 이용하도록 구성하고 있어서 Inbound NAT Rule 은 임시적으로 긴급한 상황이 아니라면 크게 사용할 일은 없어보인다.

Ingress-Nginx 설치

Kubernetes 에서 인기있는 Ingress Controller 로 Nginx 가 있다. 설치는 Helm 을 이용하면 된다.

설치 명령어를 보면, 여러가지 파라메터가 보이는데 kube-prometheus-stack 으로 설치한 prometheus 까지 고려한 것이다.

관련 명령어나 파라메터는 다음의 주소에 매우 잘 설명되어 있어서 별도로 추가설명은 하지 않는다.

쿠버네티스 prometheus 설정

Helm 을 이용해 kube-prometheus-stack 을 설치하게 되면, Grafana 를 통해서 통계 그래프를 볼 수 있다. 그런데, 몇몇 대쉬보드는 나타나지 않는데 여기서는 이 문제를 해결하는 방법을 알아본다.

Prometheus

프로메테스에서 Status > Target health 에서 상태를 같이 보면서 해결하면 좋다. 어떻게 스크랩이 설정되어 있는데, 상태가 DOWN 인 부분을 잘 살펴보면 된다.

CoreDNS

CoreDNS 대쉬보드가 아무것도 나오지 않는다. Service 에 kube-dns 혹은 Helm 으로 설치할 경우에 coredns 로 나오는데, describe 를 한번 해본다.

위에 내용을 보면 k8s-app=coredns 가 보인다. 이제 Service 에 prometheus-coredns 를 한번 보자.

Selector 를 보면 k8s-app=kube-dns 로 보인다. 이것이 맞지 않아서 관련 내용을 가지고 오지 못하는 원인다. 이것을 k8s-app=coredns 로 바꿔 준다.

이렇게 하고 Prometheus 에 Status > Target health 에서 coredns 를 보면 스크랩이 잘되는 것으로 바뀐다.

Etcd

Prometheus 에서 Status > Target health 에서 ‘http://192.168.96.60:2381/metrics‘ 로 EndPoint 로 설정되어 있다. curl 로 긁어보면 다음과 같이 나온다.

Etcd 의 경우에는 상태 모니터링을 위한 파라메터 설정을 해줘야 한다. 하지만 kubeadm 으로 K8S 를 설치한 경우에 etcd 는 Master Controll Plane 으로 동작하기 때문에 파라메터 수정을 위해서는 manifest 파일을 수정으로 해야 한다.

2381 포트를 보면, 127.0.0.1 만 엔드포인트로 되어 있다. 192.168.96.60 을 추가해 준다. 그러면 정상화 된다.

Controller Manager

Prometheus 에서 Status > Target health 에서 ‘dial tcp 192.168.96.60:10257: connect: connection refused’ 에러 메시지가 나타난다.

Controller Manager 또한 Master Controller Plane 이기 때문에 manifest 파일을 수정해줘야 한다.

bind-address 주소가 localhost 로 되어 있기 때문에 refused 된 것으로 보인다. 0.0.0.0 으로 바꾼다.

kube-scheduler

kube-scheduler 도 위에 controller manager 처럼 bind-address 가 localhost 로 되어 있다. 0.0.0.0 으로 바꾼다.

kube-proxy

kube-proxy 는 DaemonSet 오브젝트다. 설정 파일이 있는 것처럼 나오지만 설정파일은 없고, 대신 ConfigMap 으로 존재한다. 이 ConfigMap 에 metricsBindAddress 값이 없다. 이 값을 0.0.0.0 으로 지정해 준다.

DaemonSet 은 자동으로 재시작되지 않는다. Rollout 업데이트를 해준다.

kube-prometheus-stack 설치

기존에 prometheus 설치를 했었는데, 버전을 올릴겸해서 다시 설치를 했다. 설치는 Helm 을 이용했고, 기존에 설치된 버전을 삭제 처리 했다.

환경

K8S 환경이 매우 중요하다. 필자의 환경은 Handy way 방법으로 설치를 진행한 상황이기 때문에 etcd, apiserver, scheduler 가 OS 데몬으로 운영되고 있다. 따라서 Master, Worker 노드의 IP 를 필요로 한다.

만일 kubeadm 으로 설치했다면 selector 를 이용해서 라벨을 선택해주면 된다.

삭제

Helm 을 이용해서 설치했기 때문에 Helm 으로 삭제를 해야 한다. 여기서 한가지 주의해야 하는 것이 있는데, Helm 으로 삭제를 하여도 CRD(Custom Resource Definition) 은 삭제되지 않기 때문에 수동으로 삭제를 해줘야 한다.

남아 있는 crd 가 다를 수가 있지만, 반드시 삭제를 해줘야 문제가 발생하지 않는다. crd 는 다른 프로그램에서도 삭제가 되지 않는 경우가 많다.

Helm repo update

Helm 저장소를 업데이트를 해야 한다.

Download a chart and untar

Helm Chart 를 다운로드하고 압축해제 한다. 이렇게 함으로써 values.yaml 파일을 수정할 수 있게된다.

다운로드와 함께 압축을 해제해 준다.

Values.yaml 파일 수정

어떤 내용들을 수정할지는 어떤 것을 모니터링 할지, 어떤 부분이 있는지에 따라서 달라진다. 변경된 내용은 대략 다음과 같다.

nodeSelector 에 system.rule: monitoring 이 보이는데, 이는 Worker 노드에 라벨을 말한다. 선택된 라벨을 가지고 있는 Worker 노드에 Prometheus 가 설치된다. 단, 이것도 상황에 따라서 달라지는데 Node exporter 의 경우에는 각 노드에 설치가 되어야 하기 때문에 라벨을 설정하지 않는다.

Node 라벨 생성

system.rule: monitoring 이라는 라벨을 생성해야 한다.

이렇게 하면 kworker3 에 prometheus 가 설치된다.

Dependency Update

Helm 을 이용해서 설치해야 한다. 그전에 kube-prometheus-stack 에 의존성이 있는 패키지를 업데이트 해줘야 한다.

설치

Helm 을 이용해 수정한 values.yaml 을 가지고 설치를 한다.

설치가 완료 되면 Pod, Service 에 Prometheus 가 올라오게 된다.

K8S dnstools 사용하기

K8S 를 사용하다보면 여러가지 검증을 위해 Pod 를 필요로 하는 경우가 있는데, dnstools 이 유용하다. 다음과 같이 사용할 수 있다.

Pod 가 생성되면서 dnstools 컨테이너에 진입하게 되고 exit 를 하게 되면 Pod 는 삭제된다.

혹은 curl 이미지를 이용하는 방법도 있다.

Metallb v0.14.9 설치

과거 Metallb 의 버전이 0.10 이였고 K8S 버전도 1.32 이여서 최신 버전으로 다시 설치를 해봤다.

환경

K8S 환경에서 설치를 하지만 CNI 등을 고려해 설치를 진행해야 한다. 내가 설치한 환경은 좀 더 특이한데, K8S 를 Hand Way 방법으로 설치한 상태다. Hand Way 로 설치를 하게 되면 Kube-apiserver, controller, kubelet, kube-scheduler, kube-proxy 같은 것을 컨테이너 방법이 아닌 OS 프로그램 방법으로 설치를 하게 된다. K8S 에서 사용하는 인증서도 수동으로 해줘야 한다.

요약하면 다음과 같다.

  • K8S 설치: Handy way
  • K8S Version: 1.32.2
  • Containerd: 1.7.26
  • CNI: Calico

다운로드

다운로드는 CURL 이나 WGET 명령어를 이용하면 된다.

설치 문서를 보면 이 manifest 파일로 설치가 가능하다고 하지만 여러가지 환경으로 인해서 추가로 파일을 작성해야할 필요도 있다.

metallb 설치

다운로드 받은 metallb-native.yaml 파일을 적용해 설치해 준다.

설치는 아무런 문제 없이 모두 잘 설치가 된다.

IPAddressPool 생성

Metallb 에서 사용할 고정IP 대역을 Pool 로 가지고 있어야 하는데, 이것을 생성해 줘야 한다.

위와 같은 addresses 를 할당해 준다. 필자는 집에서 K8S 를 구축했기 때문에 Private IP 대역을 할당해 줬다.

생성을 위해서 파일을 적용해 주면 위와같은 오류가 발생하게 된다.

이러한 오류는 validatingwebhookconfiguration 에 metallb-webhook-configuration 설정에서 문제가 된다. Webhook 이 필요한 것인데, 이에 대한 추가적인 설정을 해줘도 되지만 일단 삭제하는 것으로 처리 한다.

이렇게 한 후에 다시 metallb-ipaddress.yaml 파일을 적용하면 잘 된다.

L2 mode

필자의 경우에는 L2 mode 로 작동되도록 해야 한다. BGP 도 있기는 하지만, 필자의 경우에는 상관 없는 것이기 때문에 L2 mode 를 위해서 다음과 같이 manifest 파일을 작성한다.

metallb-L2Advertisement.yaml 파일을 적용한다.

LoadBalancer 타입 변경

이제 고정 IP 를 필요로 하는 Service 에 타입을 ClusterIP 에서 LoadBalancer 로 변경해 본다. Metallb 설치가 정상적이라면 다음과 같이 된다.

EXTERNAL-IP 에 Metallb 의 IPAddresses Pool 중에 하나의 IP가 할당된다.

그리고 describe 결과는 다음과 같다.

EndPoints 는 Nodes IP가 보이고 Port 도 할당된게 보인다. 이건 아마도 Hand Way 설치로 인한 것으로 보인다. 원래 kubeadm 으로 설치를 하게 될 경우에 Node IP 조차도 K8S IP 여야 하는데, 여기서는 Node IP 자체가 OS 자체에 할당된 IP로 나타난다.

AWS EC2 에 OpenVPN 서버 구축

리눅스에서 OpenVPN 서버 구축해 보도록 한다. 리눅스 서버는 Ubuntu 24.04 LTS 이다.

아키텍쳐

현재 AWS 아키텍쳐는 다음과 같다.

OpenVPN 를 설치할 서버는 AWS 에 Public Subnet 에 있으며 외부에서 접속을 위해서 EIP(Elastic IP) 를 할당 해줬다. VPC 내에 모든 서버들은 172.31.0.0/16 대역폭에 자동으로 IP를 할당받아 Private IP 를 구성하도록 되어 있다.

이제 사내망 PC 에서 server1 에 Private IP 로 접속을 하기 위한 방법으로 OpenVPN 서버를 설치해보도록 한다.

OpenVPN 작동 방식

OpenVPN 의 작동방법은 NAT 기능을 이용하는 것이다. 먼저, OpenVPN 서버를 설치하고 기동하면 OpenVPN 서버는 서버에 tun0 라고 하는 네트워크 인터페이스 생성한다. 물론 이렇게 생성한 NIC에 네트워크 서브넷을 할당한다. tun0 에 IP를 할당하고 서브넷 라우팅도 함께 구성하게 된다.

이제 OpenVPN 클라이언트를 설치하고 설정파일을 읽어 연결을 하게 되면, OpenVPN 클라이언트는 PC 에 tap0 라고 하는 네트워크 인터페이스를 생성한다. 그리고 이 tap0 에는 OpenVPN 서버로부터 네트워크 관련 정보를 전달 받아서 tun0 와 동일네트워크에 IP, Defaut GW 를 구성하게 된다. 이를 다이어그램으로 표현하면 다음과 같다.

OpenVPN 을 이용하는 이유는 172.31.0.0/16 대역에 있는 서버에 접속하기 위해서다. AWS 아키텍쳐 그림상 server1 에 사내PC가 직접 접속할 방법이 없다. 하지만 OpenVPN 을 이용하면 가능하다. 다음과 같이 접속이 이루어진다.

사내PC(192.168.96.112.66) -> tap(192.168.200.56) —– 인터넷 —–> OpenVPN 서버 tun0(192.168.96.200.1) — NAT –> server1(172.31.0.43)

이러한 흐름을 보면 OpenVPN 서버의 설정에서 OpenVPN 서버와 Client 연결을 위한 네트워크 대역 할당과 server1 의 접속은 OpenVPN 서버를 거쳐서 와야하기 때문에 AWS VPC 네트워크 대역을 알려줘야 하고 OpenVPN 서버는 NAT 서버로 동작해야 한다는(그래서 iptables 을 이용한 MASQUERADE 설정을 한다.) 걸 알게 된다.

OpenVPN 2.6

현 시점에서 OpenVPN 은 2.5 를 기준으로 구분이 된다. 2.6 은 현재 최신 버전으로 사용되고 있는데, 2.5 를 기준으로해서 설정에 많은 변화가 있음으로 만일 처음 구축한다면 2.6으로 하고 호환성을 고려한다면 2.4 를 기준으로 구축하는 걸 권장 한다.

OpenVPN 설치

설치는 Ubuntu 24.04 LTS 에서 기본으로 제공하는 패키지로 설치가 가능하다. 이때 OpenVPN 이 사용할 인증서 생성을 위해서 easy-rsa 명령어도 함께 설치해야 한다.

인증서 생성

OpenVPN 서버는 Client 와 보안통신을 위해서 인증서를 필요로 한다. 인증서를 만드는 것은 그렇게 쉬운 절차가 아니여서 많은 이들이 어려움을 겪는데, easy-rsa 를 이용하면 비교적 쉽게 만들 수 있다.

인증서는 서버 인증서와 클라이언트 인증서가 필요하고 이 둘의 인증서를 만들기 위한 CA 인증서도 있어야 한다.

easy-rsa 명령어만 이용하는게 아니라 디렉토리도 있어야 하는데 /usr/share/easy-rsa/3 에서 작업해야 한다.(중요)

easyrsa 초기화

먼저 easyrsa 를 초기화 해준다. /usr/share/easy-rsa 디렉토리로 이동한다. 여기서 다음과 같이 vars 파일을 만든다.

이 파일은 CSR 내용이기 때문에 상황에 맞게 입력해주면 된다. 그리고 다음과 같이 easy-rsa 를 초기화 한다.

CA 인증서 제작

다음과 같이 root CA 인증서를 제작한다.

root CA 인증서가 생성되었다.

서버 인증서

서버 인증서를 만든다. 서버 인증서를 만들기 위해서는 도메인이 필요하다. 이 도메인은 반드시 있어야 하는건 아니고 서버-클라이언트간 보안을 위한 검증을 위한 것으로 가상으로 입력해줘도 된다.

openvpn.systemv.pe.kr 도메인은 가상으로 그냥 적은 것이다. 굳이 신경쓸 필요는 없다.

클라이언트 인증서 제작

여기서 특징이 있는데, OpenVPN 을 위한 Client 인증서는 사용자마다 제작을 해줘야 한다. 그래서 Client 이름은 ID나 이메일등을 지정해 주면 된다.

Diffie-Hellman key 생성

서버 접속을 위한 Diffie-Hellman key 를 다음과 같이 생성해 준다.

TLS-Auth key 인증 키 생성

다음과 같이 TLS-Auth 키를 생성해 준다.

생성한 키 복사

이렇게 생성한 키들을 OpenVPN 서버에 설정 디렉토리에 복사해 준다. 여기서 주의해야 할것이 배포판마다 설정 디렉토리가 다를 수 있다. Ubuntu24.04 LTS 의 경우에 /etc/openvpn 디렉토리다.

OpenVPN 서버 설정

이제 필요한 파일을 모두 갖췄으니, OpenVPN 설정을 해야 한다. 설정을 어떻게 해야할지 모르기 때문에 OpenVPN 에서 제공하는 Sample 파일을 이용해서 해보자.

이제 server.conf 파일을 다음과 같이 설정해준다.

여기서 AWS VPC CIDR 과 OpenVPN CIDR, DNS 서버 설정등을 주의해서 봐야 한다.

iptables 설정

테스트 하는 서버에는 ufw 명령어가 없다. 만일 ufw 명령어가 있다면 이것을 이용하는 방법으로 해야한다.

iptables 설정을 하는 이유는 MASQUERADE 때문이다. OpenVPN 서버가 NAT로 작동하기 위해서 Iptables 을 이용해 패킷의 흐름을 제어해야 하기 때문이다.

여기서 또 한가지, OpenVPN 서버가 실행중일 때에만 MASQUERADE 설정을 유지하고 실행하지 않을때에는 설정을 꺼야 한다. Iptables 초기화를 하면 설정이 초기화 된다. 이를 위해서 Iptables 설정과 해제 두개의 파일을 작성한다.

먼저 add-bridge.sh 라는 이름으로 쉘 스크립트를 다음과 같이 만든다.

내용이 많지만, 맨 아래 두줄이 핵심이다. tun0 네트워크 장치로부터 192.168.200.0/24 대역으로 들어온 IP에 대해서 enX0 장치를 이용해 172.31.0.0/16 대역으로 패킷을 포워딩 하고 MASQUERADE 설정을 해주고 있다.

OpenVPN 서버가 정지하면 초기화 해주는 remove-bridge.sh 파일을 만들어 준다.

모든 체인을 플러쉬하고 POSTROUTING 룰을 삭제한다.

이렇게 만들어진 파일을 systemd unit 파일인 openvpn@server 에 override 설정으로 추가해준다. override 설정은 파일을 일반 편집으로 해서 추가해주면 자동으로 알아서 추가가 된다.

이렇게 하면 openvpn 시작/중지 때마다 쉘 스크립트가 실행 된다.

시작/중지

이제 시작과 중지를 해보자. 다음과 같이 한다.

OpenVPN 클라이언트 설정

OpenVPN 홈페이지에서 Windows 용 클라이언트를 다운받아 설치한다. 설정 파일은 개인마다 가지고 있어야 해서 전체 시스템에 액세스하는 디렉토리에 넣으면 좋지 않다. 이는 설치가 끝나면 팝업으로 알려주는데, C:\Users\계정ID\OpenVPN\config 이다.

이 디렉토리에 서버에서 작성한 파일을 복사해줘야 한다. 복사해야 할 파일은 다음과 같다.

  • linux.crt (클라이언트 인증서)
  • linux.key (클라이언트 인증서 암호화 키)
  • ca.crt (CA 인증서)
  • ta.key (TLS-Auth Key)

이제 linux.ovpn 파일을 다음과 같이 작성한다.

위와같이 작성하고 OpenVPN Client 에서 설정 파일을 지정하고 연결하기 하면 연결이 된다.