기획 의도
클러스터 를 구축 하고 나서보니 내부 서비스들 에 안전하게 접근 할 수 있는 망이 필요했다. 그래서 일반적인 vpn 을 생각했었는데 예전의 경험들통해 공유기에서 L2TP 프로토콜을 통한 vpn은 속도가 너무 느렸다. 그래서 알아 보던 중 일반적인 vpn 이 사용하는 Hub and Spoke 방식의 vpn 을 우선적으로 생각 했었고 wireguard 를 생각했다.
그래서 처음에는 wireguard 를 구축 하려 시도 했는데 wireguard 를 사용 하여 vpn을 구축 했을시 한가지 한계점이 있었다. 서버가 일종의 라우터 역할 을 하게 된다는것 이었다. 이것 자체가 문제가 되는 것은 아니였는데 내가 구축 하고자하는 망의 기능 을 구현하는데 있어 부합하지 않았다.
내가 vpn을 통해서 구현 하고 싶은 목표는 다음과 같았다
- 커스텀 도메인 접속:
.ts.net같은 자동 생성 도메인이 아닌,proxmox.haru2.store와 같이 소유한 개인 도메인을 통해 서비스에 접속할 수 있어야 했다. - 보안 연결 (SSL/HTTPS): 모든 통신은 암호화되어야 하며, 브라우저에서 '안전하지 않음' 경고가 표시되지 않아야 했다.
- 사용자별 접근 제어: 특정 사용자 그룹은 A 서비스에만, 다른 그룹은 B 서비스에만 접근하는 등 세분화된 권한 제어가 가능해야 했다.
따라서 일반적인 vpn 의 방식으로는 구축이 어렵다 판단했고 여러가지를 알아보다 Tailscale vpn을 찾게 되었다.
Tailscale의 컨셉
Tailscale의 핵심 컨셉은 복잡한 네트워크 설정 없이 '인터넷을 가상의 내부망처럼' 만드는 것이다. 물리적으로 전 세계에 분산된 컴퓨터, 클라우드 서버, 모바일 기기 등 모든 장치를 하나의 가상 사설망으로 묶어, 마치 로컬 네트워크에 있는 것처럼 통신하도록 할 수 있다.
1. 제로 설정 (Zero Configuration)
전통적인 VPN이 요구하는 복잡한 방화벽 규칙 설정이나 서버 구축 과정이 없다. Tailscale은 각 기기에 프로그램을 설치하고 ID 공급자(Google, Microsoft 등)를 통해 로그인하는 것만으로 즉시 사설망을 생성하는 '제로 설정'을 지향한다. 사용자는 네트워크 전문 지식 없이도 어디서든 자신의 기기를 안전하게 연결할 수 있다.
2. 직접 통신 (Peer-to-Peer Mesh)
모든 트래픽이 중앙 서버를 경유하는 '허브 앤 스포크' 방식의 일반 VPN과 달리, Tailscale은 WireGuard® 프로토콜을 기반으로 각 기기가 최단 경로로 직접 통신하는 'P2P 메시(Peer-to-Peer Mesh)' 구조를 채택한다. 중앙의 조정 서버는 각 기기가 서로의 주소를 교환하도록 돕는 역할만 수행한다. 이 구조는 중앙 서버의 병목 현상을 제거하여 빠른 속도와 낮은 지연 시간을 보장한다.
3. 신원 기반 보안 (Identity-Based Security)
Tailscale의 보안은 네트워크 위치(예: 회사 방화벽 내부)가 아닌 '신원(Identity)'을 기반으로 한다. 이는 모든 접근을 기본적으로 신뢰하지 않는 '제로 트러스트(Zero Trust)' 보안 모델의 원칙이다. 모든 사용자와 기기는 신뢰할 수 있는 ID 공급자를 통해 인증받아야만 네트워크에 참여할 수 있다. 또한 관리자는 접근 제어 목록(ACL)을 통해 "A 그룹의 사용자는 B 태그가 붙은 서버에만 접근할 수 있다"와 같이 '누가 무엇에 접근할 수 있는지'를 세밀하게 제어 할 수 있다.
일반적인 vpn과 tailescale의 차이
일반적인 VPN과 Tailscale의 가장 큰 차이점은 연결 구조와 사용 편의성에 있다. 일반 VPN이 모든 교통이 하나의 중앙 게이트를 통과하는 '허브 앤 스포크(Hub-and-spoke)' 방식이라면, Tailscale은 각 기기가 서로 직접 최단 경로로 연결되는 '메시(Mesh)' 방식을 사용한다.
일반적인 VPN (허브 앤 스포크 모델)
일반적인 VPN은 회사나 특정 위치에 있는 중앙 VPN 게이트웨이(서버)를 중심으로 동작한다.
- 구조: 노트북, 스마트폰 등 모든 원격 기기는 먼저 중앙 게이트웨이에 접속해야 한다, 그런 다음 게이트웨이를 통해 다른 기기나 내부 네트워크의 자원에 접근한다. 즉 모든 트래픽이 이 중앙 지점을 거쳐 가야한다.
- 장점:
- 전통적으로 많이 사용되어 온 검증된 방식이다.
- 네트워크 정책을 중앙에서 관리하기 용이하다.
- 단점:
- 설정의 복잡성: VPN 서버를 구축하고, 방화벽에서 특정 포트를 열어주는 등 복잡한 네트워크 설정이 필요하다.
- 병목 현상: 모든 트래픽이 중앙 게이트웨이로 몰리기 때문에, 사용자가 많아지면 속도가 느려지거나 서버에 부하가 걸릴 수 있다.
- 트롬본 효과: 바로 옆에 있는 동료의 기기와 통신하려 해도, 데이터는 멀리 있는 중앙 게이트웨이를 왕복해야 하므로 비효율적인 경로가 발생할 수 있다.
Tailscale (P2P 메시 모델)
Tailscale은 WireGuard® 프로토콜을 기반으로, 각 기기들을 그물처럼 직접(Peer-to-Peer) 연결 하게 된다.
- 구조: 중앙 서버는 각 기기들이 서로를 찾아갈 수 있도록 주소록 역할만 할 뿐, 실제 데이터는 기기들 사이에서 최단 경로로 연결 하게 된다.
- 장점:
- 사용 편의성: 복잡한 방화벽 설정이나 포트 포워딩 없이, 각 기기에 프로그램을 설치하고 로그인만 하면 즉시 연결 할 수 있다.
- 빠른 속도와 낮은 지연 시간: 데이터가 중앙 서버를 거치지 않고 기기 간 최단 경로로 직접 전송되므로 속도가 빠르고 응답 시간이 짧다.
- 보안: WireGuard를 기반으로 동작하며, 기기 간 직접 연결은 외부 노출을 최소화할 수 있다.
- 단점:
- 중앙 조정 서버 의존: 최초 연결과 인증을 위해 Tailscale의 조정 서버에 의존 한다. (물론 Headscale이라는 오픈소스로 자체 구축도 가능합니다.)
- 공용 VPN 대체 불가: IP 우회나 지역 제한 콘텐츠 접근과 같은 일반적인 공용 VPN 서비스의 목적과는 다르기에 오직 내가 등록한 기기들 간의 사설망을 만드는 데 특화되어 있다.
아키텍쳐
위의 내용을 들을 알고 구현 을 시작 하였더라도 vpn 망을 통해 목표 기능 을 구현 하는것은 쉽지 않았다. 그 과정에서 여러가지 실수가 있었는데 결과적으로는 내가 너무 복잡하게 생각한게 문제였다.
방법 1: 초기 아키텍처 (NPM + 서브넷 라우터)
이 방법은 초기에 사이드카 패턴을 통해 각 서비스별로 테일 스케일과 연결 한다는 생각을 못한 상태로 구상한 방법이다.
- 구조: 외부 도메인 요청 → NPM 서버 → NPM이 각 서비스의 ClusterIP:Port로 전달
- 평가:
- 커스텀 도메인: ✅ 가능
- 보안 접근 (SSL): ✅ 가능
- 사용자별 접근 제어: ❌ 불가능
- 한계: 최종적으로 서비스에 접속하는 주체가 '사용자'가 아닌 'NPM 서버'가 되어버린다. 사설 ip를 노출 하지 않고 보안 연결 을 위해 npm 을 사용 했으나 이로 인해 Tailscale ACL을 통한 사용자별 서비스 접근 제어가 원천적으로 불가능해지는 '신원 상실(Identity Loss)' 문제가 발생했다.
방법 2: 이상적인 아키텍처 (사이드카 + 사설 DNS)
- 구조: 각 서비스에 Tailscale 사이드카를 배포하고, 내부 DNS 서버를 직접 구축한다. Tailscale의 Split DNS 기능으로 커스텀 도메인(
proxmox.haru2.store)에 대한 질의를 내부 DNS 서버로 보낸다. 내부 DNS는 해당 서비스의 Tailscale IP를 반환하여 사용자가 직접 서비스에 접속하도록 한다. - 평가:
- 커스텀 도메인: ✅ 가능
- 보안 접근 (SSL): ✅ 가능
- 사용자별 접근 제어: ✅ 가능
- 한계: 프로젝트의 3대 목표를 모두 달성하는 가장 완벽하고 정석적인 방법이다. 하지만 개인 IDC 환경에서 DNS 서버까지 직접 구축하고 운영하는 것은 과도한 복잡성(Over-engineering)을 유발한다고 판단했다.
(참고: 이 과정에서 공인 DNS(가비아)에 Tailscale IP를 직접 등록하는 방법을 고려했으나, 이는 내부 사설 IP를 외부에 노출 해야하는 보안 허점이라 생각해 폐기했다.)
방법 3: 현실적인 타협안 (MagicDNS + 사이드카)
- 구조: 각 서비스에 Tailscale 사이드카를 배포하고, Tailscale이 자동으로 생성해주는 MagicDNS 주소(
서비스명.테일넷명.ts.net)를 사용한다. 접근 제어는 Tailscale ACL로 관리한다. - 평가:
- 커스텀 도메인: ❌ 불가능
- 보안 접근 (SSL): ✅ 가능
- 사용자별 접근 제어: ✅ 가능
- 결론: 사설 도메인'이라는 편의성을 포기하는 대신, 아키텍처의 복잡성을 대폭 낮출 수 있었고 핵심 기능들을 구현 할 수 있었다.
K8S 클라스터 서비스에 테일스케일을 사이드카로 배포하기
아래 내용 부터는 클러스터의 서비스에 테일스케일 을 사이드 카로 배포하는 과정을 담고 있음.
Proxmox
1. 사전 준비
- Proxmox VE가 인터넷에 연결되어 있어야 함.
- root 권한 또는 sudo 권한이 필요.
- 설치할 Tailscale 계정 준비(Google, Microsoft 등으로 로그인 가능).
2. Tailscale 설치
2-1. 패키지 업데이트
sudo apt update
sudo apt upgrade -y
2-2. Tailscale 패키지 설치
Tailscale 공식 리포지토리를 사용:
curl -fsSL https://tailscale.com/install.sh | sh
이 스크립트는 자동으로 apt 리포지토리를 등록하고 Tailscale을 설치.
3. Tailscale 서비스 시작
3-1. Tailscale 활성화
sudo tailscale up
처음 실행 시 인증 URL이 출력됨, 브라우저에서 해당 URL로 접속하여 로그인 후 인증.
3-2. 상태 확인
tailscale status
Tailscale IP, 연결된 노드, 온라인 상태 확인 가능.
4. 자동 시작 설정
Tailscale 서비스가 Proxmox 재부팅 시 자동으로 시작되도록 설정:
sudo systemctl enable tailscaled
sudo systemctl start tailscaled
5. 방화벽/라우팅 설정 (선택 사항)
5-1. LAN 접근
- Proxmox 호스트와 Tailscale 네트워크 간 트래픽 허용:
sudo tailscale up --accept-routes
5-2. 서브넷 라우팅
- 특정 서브넷을 Tailscale 네트워크로 라우팅하려면:
sudo tailscale up --advertise-routes=192.168.1.0/24
필요 시 ACL 정책 설정으로 허용된 사용자만 접근 가능.
6. Proxmox VM 접근
- Tailscale IP로 Proxmox 웹 GUI 접속 가능.
- 예:
https://100.x.x.x:8006
6.1 Tailscale serve
- tailscale 의 프록시 기능을 사용해 80 또는 443 으로 접근 가능
- 사이드카 띄우면 소켓 경로가
/tmp/tailscaled.sock로 바뀌는데 아직 원인을 모름.
#https 보안연결 사용
tailscale --socket=/tmp/tailscaled.sock serve --bg https+insecure://localhost:3000
7. 확인
tailscale status # 연결 확인
tailscale serve status # serve 상태 확인
tailscale ip # 할당된 Tailscale IP 확인
팁
tailscale up옵션으로 ACL, exit-node, subnet routes 등 다양한 설정 가능.tailscale logout후 다른 계정으로 로그인 가능.- Proxmox 클러스터 환경에서는 모든 노드에 Tailscale 설치 후 연결하면 쉽게 원격 관리 가능.
Harbor
구조
[]= 파드
[하버] - [ ingress + tailscale sidecar(nginx) ]
- 하버는 레지스트리 접근을 위한 도메인을 노출하기 위해 ingress를 사용함
- tailscale serve 는 127.0.0.1 또는 localhost 만 프록시 가능함
- 따라서 ingress 파드에 tailscale sidecar를 같이 배포하고, sidecar 내부에서 nginx 를 사용하여 localhost 로 하버의 core, portal에 접근할 수 있는 프록시를 구성해야 함
- 즉 하버와 tailscale을 연결하는 과정은 ingress를 다루는 과정임
도메인
- 내부(백엔드/젠킨스):
harbor.192.168.0.111.nip.io← ingress 가 노출 - 외부(개발자 UI):
http://harbor.tail2dac17.ts.net← tailscale magic dns 로 노출
harbor.conf 설정
server {
listen 9090 ssl;
server_name harbor.tail2dac17.ts.net;
ssl_certificate /etc/nginx/certs/harbor.crt;
ssl_certificate_key /etc/nginx/certs/harbor.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Portal
location / {
proxy_pass harbor.192.168.0.111.nip.io; # 본인의 harbor portal pod 의 ClusterIP 에 맞게 설정
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
# Core
location /api/ {
proxy_pass http://10.102.26.224:80; # 본인의 harbor core pod 의 ClusterIP 에 맞게 설정
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
# Service
location /service/ {
proxy_pass http://10.102.26.224:80; # core
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
# Registry v2
location /v2/ {
proxy_pass http://10.102.26.224:80; # core
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
# Chartmuseum
location /c/ {
proxy_pass http://10.102.26.224:80; # core
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
# Optional: HTTP → HTTPS 리다이렉트
#server {
# listen 80;
# server_name harbor.tail2dac17.ts.net;
# return 301 https://$host$request_uri;
#}
CA 인증서
- tailscale 을 통해 SSL 인증 사용 가능
- 단, Harbor 는 자체적으로 CA 를 발급하지 않으며, 사용자가 직접 TLS 인증서를 설정해야 함
- tailscale 을 통해 접근하는 사용자가 Harbor 에 설정된 TLS 인증서를 신뢰하도록 구성해야 함
- self-signed 인증서 또는 내부 CA 로 발급한 인증서를 사용하면 보안 연결은 가능하지만 브라우저/클라이언트에서 “신뢰되지 않음” 경고가 발생할 수 있음
- 가능하다면 공인 SSL 인증서를 발급받아 사용하는 것을 권장
- 인증서 추출 과정은 여기로 → (링크 추가 예정)
Dockerfile
# tailscale 이미지
FROM tailscale/tailscale:latest
# Install nginx
RUN apk add --no-cache nginx
# 80 포트 점유 해제를 위해 기본 설정 제거
RUN rm /etc/nginx/http.d/default.conf
# nginx 설정 파일 복사
# nginx.conf 에서 conf.d 경로의 설정을 http 블록 내부로 포함해야 함
# 따라서 별도로 설정 후 주입
COPY nginx.conf /etc/nginx/nginx.conf
COPY harbor.conf /etc/nginx/conf.d/harbor.conf
# harbor CA 인증서 복사
RUN mkdir -p /etc/nginx/certs
COPY harbor-ca.crt /etc/nginx/certs/harbor.crt
COPY harbor-ca.key /etc/nginx/certs/harbor.key
# startup script 복사 및 권한 설정
COPY start.sh /start.sh
RUN chmod +x /start.sh
# 컨테이너 시작 시 실행할 명령어
CMD ["/start.sh"]
start.sh
#!/bin/sh
set -e
echo "Starting tailscaled..."
tailscaled &
echo "Running tailscale up..."
tailscale up --authkey=${TS_AUTHKEY} --hostname=${TS_HOSTNAME} --exit-node= --accept-dns=false
# Nginx 먼저 실행 (백그라운드 모드)
echo "Starting Nginx..."
nginx &
# Nginx가 완전히 뜰 때까지 잠시 대기
sleep 3
# tailscale serve 실행 (HTTPS)
# nginx 가 ssl 연결을 하고 있기에 여기서는 443 포트만 할당.
echo "Starting tailscale serve..."
tailscale serve --bg --tcp 443 localhost:9090
# 컨테이너가 종료되지 않도록 유지
tail -f /dev/null
ingress-nginx-values
USER-SUPPLIED VALUES:
controller:
service:
type: LoadBalancer
# 기존 볼륨에 새로운 볼륨 추가
# tun 은 tailscale 이 가상 네트워크에 연결하기 위해 필요한 디바이스 파일
extraVolumes:
- name: tun-device
hostPath:
path: /dev/net/tun
type: CharDevice
extraContainers:
- name: tailscale
image: harbor.192.168.0.110.nip.io/harbor/tailscale-nginx-harbor:latest
# 레지스트리에서 받아오도록 설정되어 있지만 ingress 가 해당 도메인을 노출하고 있음
# 따라서 ingress 배포 중 pull 을 받을 수 없음
imagePullPolicy: IfNotPresent
# 따라서 내부에 존재하는 이미지를 사용하도록 설정
env:
- name: TS_KUBE_SECRET
value: harbor-tailscale-state
- name: TS_HOSTNAME
value: harbor
- name: TS_AUTHKEY
valueFrom:
secretKeyRef:
name: harbor-tailscale-auth-secret
key: TS_AUTHKEY
securityContext:
capabilities:
add:
- NET_ADMIN
- NET_RAW
resources: {}
volumeMounts:
- name: tun-device
mountPath: /dev/net/tun
nginx-ingress 배포 과정
# 빌드
docker buildx build --platform linux/amd64,linux/arm64 \
-t harbor.192.168.0.110.nip.io/harbor/tailscale-nginx-harbor:latest .
# 푸시 <- 안 해도 무방. 어차피 pull 불가
docker push harbor.192.168.0.110.nip.io/library/tailscale-nginx-harbor:latest
# 빌드된 이미지 추출
docker save harbor.192.168.0.110.nip.io/library/tailscale-nginx-harbor:latest \
-o tailscale-nginx-harbor.tar
# 각 노드로 전송
# 노드가 6개라면 6개 노드 전부 전송해야 함
scp tailscale-nginx-harbor.tar worker@192.168.0.16:/tmp/
# 쿠버네티스에 이미지 등록
# 각 노드에 접속하여 실행
sudo ctr -n k8s.io images import /tmp/tailscale-nginx-harbor.tar
시크릿 생성
- Tailscale auth key secret (서비스별 별도)
- Tailscale state secret (빈 상태로 생성) ← 테일 스케일 상태 저장을 위해 사용.
kubectl create secret generic harbor-tailscale-auth-secret \
--from-literal=TS_AUTHKEY='tskey-auth-xxxx' \
-n ingress-nginx
kubectl create secret generic harbor-tailscale-state -n ingress-nginx
tailscale 상태 시크릿을 위한 RBAC 설정
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ingress-tailscale-role
namespace: ingress-nginx
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "create", "update", "patch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "create", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ingress-tailscale-binding
namespace: ingress-nginx
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx
roleRef:
kind: Role
name: ingress-tailscale-role
apiGroup: rbac.authorization.k8s.io
Role: ingress-tailscale-role
ingress-nginx네임스페이스에서 tailscale sidecar가 필요로 하는 Secret과 Event 리소스에 대한 권한을 정의함.- Secrets: Tailscale 상태 저장용 Secret(
harbor-tailscale-state)에 접근, 생성, 갱신, 패치 가능. - Events: 사이드카 상태나 이벤트를 기록하기 위해 Event 생성/조회 가능.
RoleBinding: ingress-tailscale-binding
- 정의한 Role을 실제 사용자 또는 서비스 계정에 연결
- 여기서는
ingress-nginx서비스 계정에ingress-tailscale-role권한을 부여하여, Ingress Controller 내 tailscale sidecar가 Secret과 Event를 사용할 수 있도록 허용
harbor RBAC 적용
RBAC 적용
kubectl apply -f harbor-tailscale-rbac.yaml
Harbor ingress 업데이트
# 하버 롤링 업데이트
helm upgrade harbor harbor/harbor -f harbor-values.yaml -n harbor
#ingress 롤링 업데이트
helm upgrade ingress-nginx ingress-nginx/ingress-nginx \
-n ingress-nginx \
-f values-tailscale.yaml
트러블슈팅
tailscale 컨테이너 안에 nginx 를 붙인 이유
- 원래는 Harbor Portal의 ClusterIP에 socat을 사용해 내부 포트로 매핑하고, tailscale serve를 이용하려고 했음.
- 보안 연결은
http://를 통해 tailscale이 연결하도록 계획했음. - 초기 설정 예시:
socat TCP-LISTEN:9090,bind=127.0.0.1,fork TCP:10.100.82.126:80
tailscale serve --bg http://127.0.0.1:9090
발생한 에러와 원인
tailscale 컨테이너 안에 nginx 를 따로 설치한 이유
- 403 Forbidden
- 원인:
- Harbor는 로그인, 이미지 Push/Pull 등 사용자 인증이 필요한 요청에 대해 HTTPS를 요구함.
- 하지만 socat은 L4 프록시이므로 HTTPS 헤더를 올바르게 전달하지 못함.
- 80 포트로 연결할 경우에도 동일하게 403 에러 발생.
- 해결: nginx를 통해 ssl 인증을 처리하도록 변경
- 원인:
- 405 Method Not Allowed
- 원인:
- nginx로 9090 포트로 들어오는 요청을 Harbor Core와 Portal을 구분하지 않고 모두 Portal로 프록시함
- Portal이 허용하지 않는 HTTP 메서드 요청이 전달되어 발생
- 해결: 각 API 경로(core, portal, service 등)에 맞춰 nginx에서 개별 라우팅 설정 필요
- 원인:
- 404 Not Found
- 원인 (예외적 상황):
- 같은 파드 안에 Ingress가 존재하면 바로 Ingress로 보내면 될 것이라 생각했음
- 그러나 Ingress 컨테이너는 80,443 포트를 열고 있음에도 외부에서 접근 시 경로를 찾지 못함
- 정확한 원인은 파악되지 않음
- 원인 (예외적 상황):
결론 및 결정
- Ingress를 직접 수정하는 방법도 가능하지만,
- Harbor + Ingress 구조를 정확히 이해하지 못함
- 쿠버네티스 선언적 구조(Declarative)를 위배하게 됨
- 역할을 명확히 분리하기 어려움
- 따라서 nginx를 통한 프록시 서버를 sidecar로 두어, 각 Harbor API 경로(core, portal, service 등)로 안전하게 라우팅하도록 결정
Grafana
구조
- 별도 이미지 없음
- Deployment에 사이드카 수동 반영
- 같은 파드 안에 사이드카 컨테이너 라서 localhost:3000으로 바로 접근 가능
서비스 설정
테일스케일 사이트카 를 붙이고 나서 로컬에서 접근이 안되어 이게 문젠가 싶어 기존에 설정했던 ExternalIp 를 제거 하였는데 꼭 ClusterIp 타입여야만 하는지는 아직 검증안됨
helm upgrade prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--set grafana.service.type=ClusterIP
prometheus-grafana-values
- name: tailscale
image: tailscale/tailscale:latest
imagePullPolicy: Always
env:
- name: TS_KUBE_SECRET
value: prometheus-grafana-tailscale-state
- name: TS_HOSTNAME
value: grafana
- name: TS_AUTHKEY
valueFrom:
secretKeyRef:
name: grafana-tailscale-secret
key: TS_AUTHKEY
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
securityContext:
capabilities:
add:
- NET_ADMIN
- NET_RAW
시크릿 생성
- Tailscale auth key secret (서비스별 별도)
- Tailscale state secret (빈 상태로 생성) ← 테일 스케일 상태 저장을 위해 사용.
kubectl create secret generic grafana-tailscale-auth-secret \
--from-literal=TS_AUTHKEY='tskey-auth-xxxx' \
-n monitoring
kubectl create secret generic grafana-tailscale-state -n monitoring
tailscale 상태 시크릿을 위한 RBAC 설정
# grana-tailscale-rbac.yaml (수정·권장 버전)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tailscale-sidecar-role
namespace: monitoring
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "create", "update", "patch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "create", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tailscale-sidecar-binding
namespace: monitoring
subjects:
- kind: ServiceAccount
name: prometheus-grafana
namespace: monitoring
roleRef:
kind: Role
name: tailscale-sidecar-role
apiGroup: rbac.authorization.k8s.io
grafana RBAC 적용
RBAC 적용
kubectl apply -f grafana-tailscale-rbac.yaml
grafana upgrade
helm upgrade prometheus grafana/grafana \
-n monitoring \
-f values.yaml
Tailscale serve
tailscale --socket=/tmp/tailscaled.sock serve --bg http://localhost:3000
Jenkins
구조
[젠킨스 도커 이미지 + tailscale sidecar] (StatefulSet)
하버 에서 이미지 풀 하기
harbor.192.168.0.110.nip.io 주소로 하버에서 이미지를 가져 오려면 스케일링 을 하는 파드 특성상 모든 노드가 하버 인증서를 신뢰 하고 있어야함 .
따라서 ConfigMap + DemonSet 을 통해 모든 노드에 일괄적으로 인증서를 배포 할수 있음.
자세한 내용 → https://developsvai5096.tistory.com/144
Dockerfile
FROM jenkins/jenkins:2.516.1-lts-jdk21
USER root
# 빌드에 필요한 패키지 추가 설치
RUN apt-get update && apt-get install -y \
git \
nginx \
net-tools \
curl \
vim \
ca-certificates
USER jenkins
push
docker push harbor.192.168.0.110.nip.io/jenkins/custom-jenkins:latest
jenkins-values
controller:
image:
registry: "harbor.192.168.0.110.nip.io"
repository: "jenkins/custom-jenkins"
tag: "latest"
imagePullSecrets:
- name: harbor-creds
persistence:
enabled: true
storageClassName: "local-path"
size: "10Gi"
sidecars:
additionalSidecarContainers:
- name: tailscale
image: tailscale/tailscale:latest
env:
- name: TS_KUBE_SECRET
value: jenkins-tailscale-state
- name: TS_HOSTNAME
value: jenkins
- name: TS_AUTHKEY
valueFrom:
secretKeyRef:
name: jenkins-tailscale-auth-secret
key: TS_AUTHKEY
# POD_NAME과 POD_UID를 Downward API를 통해 주입
# 젠킨스는 이 설정이 필요
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_UID
valueFrom:
fieldRef:
fieldPath: metadata.uid
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: Always
securityContext:
capabilities:
add:
- NET_ADMIN
- NET_RAW
시크릿 생성
- Tailscale auth key secret (서비스별 별도)
- Tailscale state secret (빈 상태로 생성) ← 테일 스케일 상태 저장을 위해 사용.
kubectl create secret generic jenkins-tailscale-auth-secret \
--from-literal=TS_AUTHKEY='tskey-auth-xxxx' \
-n jenkins
kubectl create secret generic jenkins-tailscale-state -n jenkins
tailscale 상태 시크릿을 위한 RBAC 설정
# jenkins-tailscale-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tailscale-sidecar-role
namespace: jenkins # jenkins 네임스페이스에 생성
rules:
- apiGroups: [""] # Core API group
resources: ["secrets"]
# 특정 시크릿에만 접근하도록 제한하여 보안을 강화합니다.
resourceNames: ["jenkins-tailscale-state"]
verbs: ["get", "update", "patch", "create"] # 오류 해결에 필요한 권한
- apiGroups: [""] # Core API group
resources: ["events"]
verbs: ["get", "create", "patch"] # 경고 해결에 필요한 권한
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tailscale-sidecar-binding
namespace: jenkins # jenkins 네임스페이스에 생성
subjects:
- kind: ServiceAccount
# Jenkins Helm 차트는 보통 릴리스 이름과 동일한 서비스 어카운트를 생성.
name: jenkins
namespace: jenkins
roleRef:
kind: Role
name: tailscale-sidecar-role # 위에서 생성한 Role의 이름
apiGroup: rbac.authorization.k8s.io
RBAC 적용
kubectl apply -f jenkins-tailscale-rbac.yaml
jenkins upgrade
helm upgrade --install jenkins jenkins/jenkins -f jenkins-values.yaml -n jenkins'인프라' 카테고리의 다른 글
| [K8S] configMap 과 Demonset을 활용해 인증서 배포하기 (1) | 2025.08.09 |
|---|---|
| [K8S] Harbor : LoadBalancer 및 TLS 설정 (0) | 2025.06.17 |
| [K8S] 클러스터의 리소스 분배 전략 (0) | 2025.06.15 |
| [Jenkins] Jenkins 빌드 머신 설정 하기 (0) | 2025.06.14 |
| [Jenkins] Helm으로 Jenkins 설치하기 (0) | 2025.06.13 |