Swagger UI에서 여러 서비스에 대한 문서를 효율적으로 관리하려면, 문서 파일의 저장소와 접근 방식에 대한 고민이 필요하다. 이 글에서는 Swagger UI를 쿠버네티스 환경에서 구성하는 방법과, Object Storage를 활용해 Swagger 문서를 제공하는 방법을 다룬다. 이를 통해 자동화된 문서 관리, 안정적인 시스템 구축을 목표로 한다. 또한, Swagger UI를 조직의 필요에 맞게 커스터마이징하여 제공하는 방법도 함께 살펴볼 것이다.
1. Swagger UI
1-1. Swagger UI란?
Swagger UI는 swagger-api Organization의 핵심 프로젝트이다. 사용자에게 직관적인 UI를 제공하여 API 제공자와 사용자 간의 협업에 큰 도움을 주는 툴이다.
- Organization: https://github.com/swagger-api
- Swagger UI Project: https://github.com/swagger-api/swagger-ui
1-2. Swagger UI의 기능 간단 소개
Swagger UI는 HTTP URL 경로에 접근해 문서를 로드하도록 설계되어 있다.
Swagger UI에 입력한 경로로 들어가보면 스웨거 문서가 호스팅되고 있다는 사실을 알 수 있다.
Swagger UI는 위와 같은 문서를 기반으로, 자체 로직을 통해 UI를 구성하여 사용자에게 아래와 같은 예쁜 화면을 보여주는 것이다.
또한, Swagger UI는 단지 API의 스펙만을 보여주는 것이 아니라 API를 직접 호출해볼 수 있다. API 스펙을 열어 Try it out 버튼을 클릭하고 Execute를 클릭하면 실제 Response 값을 받아볼 수 있다.
이 외에도 Security를 설정해 API-KEY 방식이나 토큰을 넣어 인증을 테스트해볼 수 있는 기능 등 다양한 기능을 제공한다.
1-3. Swagger UI를 활용하려면
우선, 팀에서 사용하는 인프라에 Swagger UI 서버를 배포해야 한다. 또한, 위에서 설명한 것처럼 스웨거 문서를 호스팅하여 Swagger UI가 이를 참조할 수 있도록 설정해야 한다.
epages의 restdocs-api-spec와 같은 써드파티 라이브러리를 사용하는 경우, 서버의 코드만으로 수정할 수 없는 부분들이 존재할 수 있다. 필요한 기능이 있다면 Swagger UI 프로젝트를 직접 내려받아 코드를 수정해 사용하는 방법도 고려해야 한다.
이처럼 Swagger UI를 사용하는 여정은 쉽지 않다. 하지만 지금부터 차근차근 알아가 보도록 하자.
2. 나만의 Swagger UI 만들기 전에
2-1. 자동화
우리가 Swagger UI를 사용하려면 1.Swagger 문서 생성하고, 2.Swagger UI가 참조할 수 있는 저장소에 그 문서를 업로드해야 한다. 물론 문서를 수동으로 업로드하는 것도 가능하다. 하지만 처음 한 두번은 기쁜 마음으로 작업하더라도, 시간이 지나면 문서를 추출하고 업로드하는 과정이 점차 부담으로 느껴질 것이다.
하지만 1번과 2번의 과정을 CI/CD 파이프라인에 통합할 수 있다면 장기적으로 리소스를 절약할 수 있다.
2-2. 업데이트 시점
그렇다면 Swagger 문서를 업로드하는 적절한 "시점"은 언제일까?
이는 그 서버를 사용하는 대상에 따라 달라질 수 있다.
팀 내부의 프론트엔드 개발자에게 API 스펙을 제공해야 한다면, DEV 서버를 배포하는 시점에 Swagger가 업데이트하는 것이 적합할 것이다. 반면 외부 사용자에게 API를 제공한다면 운영 환경이 배포될 때 Swagger 문서를 업데이트해야 한다.
이처럼 회사나 팀의 상황에 따라 적절한 "업데이트 시점"을 선택하는 것이 중요하다.
2-3. 스웨거 문서 업로드
Swagger 문서를 생성하고, Swagger UI가 참조할 수 있는 문서를 업로드하는 과정을 Jenkins 파이프라인에 통합할 것이다. 기존 Dev 서버나 Production 배포 파이프라인에 이 작업을 추가하면, 서버가 업데이트됨과 동시에 Swagger UI도 자동으로 최신 상태를 유지할 수 있다.
Swagger 문서를 Object Storage에 업로드하고 Swagger UI에서 Object Storage를 참조하게 할 것이다. 내부에서만 사용되는 API 문서는 공개하지 않는 것이 좋다고 생각한다. 문서가 공개되어 있으면 악의적인 사용자에 의해 서버가 공격을 당할 가능성이 높아지기 때문이다. 따라서 Object Storage를 정적 호스팅하지 않고 숨긴 상태로 둘 것이다.
참고!
이미 외부에 공개된 API라면 openapi3 파일을 공개적으로 호스팅하는 방법도 좋다고 생각한다. 구현이 훨씬 간단해지기 때문이다.
플로우는 아래와 같다.
- 사용자가 Jenkins에서 Build를 실행한다.
- Jenkins가 Git Repository에서 프로젝트를 가져온다.
- Jenkins는 프로젝트에서 openapi 명령어를 통해 스웨거 문서를 생성한다.
- 생성한 문서를 Object Storage에 업로드한다.
- 스웨거 UI는 업로드된 스웨거 문서를 통해 새로운 UI를 그려준다.
3. Swagger 문서 업로드 파이프라인 추가하기
3-1. 먼저 문서 자동화
1.Swagger 문서 만들기를 CI/CD 파이프라인에서 자동화하려면, 애플리케이션 코드 수준에서 Swagger 문서가 자동으로 생성되도록 설정해야 한다.
아래 글에서 Spring REST Docs와 Epages의 restdocs-api-spec를 활용하여 Swagger API 문서 자동화하는 방법을 소개하고 있다. 이를 참고하면 손쉽게 Swagger 문서 자동화를 적용해볼 수 있을 것이다.
3-2. Jenkins AWS Steps 플러그인 사용
Jenkins를 사용해 Swagger 문서 형식인 openapi3.yaml 혹은 openapi3.json 파일을 Object Storage에 업로드할 것이다. 그런데 파일을 업로드하려면 어떻게 해야 할까? Jenkins의 플러그인을 활용하면 효율적으로 작업할 수 있다.
Jenkins의 가장 큰 장점 중 하나는 강력한 플러그인 생태계가 있다는 것이다.
AWS Steps라는 플러그인은 S3 업로드 기능을 제공하며 아래 단계를 통해 Jenkins 파이프라인에 적용해볼 수 있다.
- AWS Steps 플러그인을 설치.
- 인프라 비밀키를 등록. NCloud를 사용하고 있다면 Username에 access_key를 넣고, Password에 secret_key를 입력해주면 된다.
- Jenkins 파이프라인에서 AWS Steps의 기능을 사용할 때 비밀키를 사용
3-3. 스크립트 작성
이제 기존 CI/CD 파이프라인에 "Swagger Docs Upload"라는 Stage를 추가할 것이다.
아래는 swagger라는 버킷의 openapi라는 경로에 파일을 업로드하는 예시이다.
- SOURCE_BRANCH와 PROJECT_NAME 환경변수는 파이프라인을 실행 전에 사용자 입력을 통해 받아오도록 설정하자.
- TARGET_FILE_PATH는 API 문서 자동화를 통해 생성된 스웨거 문서의 경로를 지정한다.
- 등록했던 비밀키 NCLOUD_KEY를 withAWS라는 함수에 credentials로 전달한다.
pipeline {
agent any
environment {
TARGET_FILE_PATH = '/build/resources/main/static/docs/'
}
stages {
stage('Checkout') {
steps {
script {
GIT_VALUE = git branch: '${SOURCE_BRANCH}', credentialsId: 'GIT', url: '${GIT_URL}'
env.GIT_COMMIT = GIT_VALUE.GIT_COMMIT
}
}
}
stage('Swagger Upload') {
when {
anyOf {
expression { env.PROJECT_NAME == 'shop' }
expression { env.PROJECT_NAME == 'admin' }
}
}
steps {
sh './gradlew clean :${PROJECT_NAME}:openapi3'
withAWS(region: 'kr-standard', endpointUrl: 'https://kr.object.ncloudstorage.com', credentials: 'NCLOUD_S3') {
s3Upload(
bucket: 'swagger',
file: PROJECT_NAME + TARGET_FILE_PATH + PROJECT_NAME + '-openapi3.yaml',
path: 'openapi/')
}
}
}
// ... Build ...
}
}
4. Swagger UI 커스터마이징하기
4-1. 기본 컨셉
Swagger UI 프로젝트의 코드를 직접 수정해서 Docker Image로 빌드해, 나만의 Swagger UI를 만들 것이다.
Swagger UI 프로젝트를 조금만 살펴보면, docker라는 폴더에 인프라 커스터마이징을 위한 핵심 요소들이 모여 있다. 이를 통해 필요에 따라 코드를 수정하여 자신만의 환경에 맞게 활용할 수 있다. (개인적으로 굉장히 Customizable-friendly하다고 느꼈다.)
또한, docs/customization 디렉토리에는 Swagger UI 커스터마이징에 대한 문서가 잘 정리되어 있으니, 참고하면 도움이 될 것이다.
4-2. Swagger UI 프로젝트 받아오기
먼저 Swagger UI 프로젝트를 clone 받아보자.
원하는 버전을 선택하면 된다. 예시에서는 v4.11.1 Tag로 checkout했다. 이제 Swagger UI를 커스터마이징할 준비를 마쳤다.
$ git checkout v4.11.1
4-3. 커스터마이징 예시 - URL 추가하기
간단한 커스터마이징 예시 하나를 준비했다. Swagger UI에 여러 개의 URL을 추가하여, 유저가 드롭다운을 통해 다양한 API 문서를 접근할 수 있도록 만들 것이다.
우리는 이 작업을 수정하기 위해 swagger-ui 프로젝트의 dist/swagger-initializer.js 파일을 수정할 것이다.
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "https://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
//</editor-fold>
};
위 코드에서 url을 urls로 수정하고 url 객체를 담은 배열 형태로 수정하면 끝이다.
window.ui = SwaggerUIBundle({
urls: [
{url: "/openapi3/admin-openapi3.yaml", name: 'Admin'},
{url: "/openapi3/shop-openapi3.yaml", name: 'Shop'}
],
//..
});
이제 커스터마이징을 마친 Swagger UI를 Docker 이미지로 빌드해보자.
5. Docker 이미지 만들기
우리는 아래와 같은 구성을 만들 것이다.
5-1. S3 마운트 준비하기
위의 이미지처럼 우리는 Swagger UI 서버가 Object Storage에 접근할 수 있도록 설정할 것이다. 서버가 안정적으로 Object Storage에 접근하려면 스토리지를 마운트해야 한다.
Swagger UI 프로젝트는 root 디렉토리에 Dockerfile을 구성해 두었으며, 우리는 Swagger UI의 실행 시점에 S3 Mount를 할 수 있도록 Dockerfile에 수정할 것이다. S3 파일 시스템에 접근하기 위해 s3fs라는 라이브러리를 사용할 것이다.
- swagger-ui/Dockerfile
FROM nginx:1.21.6-alpine
ARG S3_ACCESS_KEY
ARG S3_SECRET_KEY
RUN apk add automake autoconf fuse-dev libtool build-base git curl-dev libxml2-dev make openssl-dev && \
git clone https://github.com/s3fs-fuse/s3fs-fuse.git && \
cd s3fs-fuse && \
chmod +x autogen.sh && \
chmod +x configure.ac && \
./autogen.sh && \
./configure && \
make && \
make install && \
echo "${S3_ACCESS_KEY}:${S3_SECRET_KEY}" > /etc/passwd-s3fs && \
chmod 600 /etc/passwd-s3fs && \
mkdir /swagger
RUN apk update && \
apk add --no-cache "nodejs>=14.17.6-r0"
LABEL maintainer="fehguy"
ENV API_KEY "**None**"
ENV SWAGGER_JSON "/openapi/openapi3.yaml"
ENV PORT 8080
ENV BASE_URL ""
ENV SWAGGER_JSON_URL ""
COPY --chown=nginx:nginx --chmod=0666 docker/nginx.conf ./docker/cors.conf /etc/nginx/
# copy swagger files to the `/js` folder
COPY --chmod=0666 ./dist/* /usr/share/nginx/html/
COPY --chmod=0555 ./docker/docker-entrypoint.d/ /docker-entrypoint.d/
COPY --chmod=0666 ./docker/configurator /usr/share/nginx/configurator
COPY start.sh /start.sh
RUN chmod +x /start.sh
RUN chmod 777 /usr/share/nginx/html/ /etc/nginx/ /var/cache/nginx/ /var/run/
EXPOSE 8080
ENTRYPOINT ["/start.sh"]
- swagger-ui/start.sh
#!/bin/sh
# s3fs 명령어 실행
s3fs swagger /swagger -o allow_other -o url=https://kr.object.ncloudstorage.com -o umask=0000
# 권한 부여
chmod -R 755 /swagger/openapi
# Nginx 실행
nginx -g 'daemon off;'
여기서 주의해야 할 점이 있다. 마운트한 폴더 자체에 권한을 부여하지 않으면 스웨거 문서가 업데이트될 때마다 파일이 권한이 리셋되어 Swagger UI 서버가 문서에 접근하려고 할 때 403에러가 발생할 수 있다. 이를 방지하려면 s3fs 명령어를 실행할 때, umask 옵션을 사용하여 권한을 설정해야 한다.
5-2. NGINX
이제 Object Storage에 저장된 문서를 가져올 수 있다. 도커를 실행하면 나만의 Swagger UI를 사용할 수 있게 되지만, 사실 작업이 하나 더 필요하다. 바로 NGINX다.
Swagger UI는 기본적으로 HTTP URL 경로에서 문서를 로드하도록 설계되어 있다. 따라서 파일 경로(file://)로 직접 접근하려고 하면 아래와 같이 CORS 오류를 터트려버린다.
이 문제를 해결하기 위해 NGINX를 사용하는 것이다. swagger-ui 프로젝트의 docker 디렉토리를 살펴보면, nginx.conf 가 존재한다. 마치 "나 커스터마이징해주세요."라는 외치는 것 같다.
우리는 이 파일을 수정하여 마운트된 폴더의 스웨거 파일을 Swagger UI 서버와 동일한 호스트에서 호스팅하도록 설정할 것이다. 아래와 같이 코드 단 4줄만 추가하면 된다. Swagger UI의 호스트에 접근할 때 /openapi라는 경로로 요청을 보내면, NGINX가 그 요청을 마운트된 폴더 경로인 /swagger/openapi 로 연결해줄 것이다.
http {
// 기본설정 ...
server {
// 기본설정 ...
location /openapi {
alias /swagger/openapi; # S3 버킷이 마운트된 경로
autoindex on; # 디렉터리 목록 표시를 위한 설정
include cors.conf; # CORS 설정 포함
}
}
}
5-3. 도커 이미지 생성 및 업로드
이제 아래 명령어를 통해 이미지를 생성해보자.
$ docker buildx build --platform linux/amd64 \
--build-arg S3_ACCESS_KEY=${엑세스키} \
--build-arg S3_SECRET_KEY=${시크릿키} \
-t my-sever/swagger-ui:v1.0.0 .
이제 해당 이미지를 Docker Hub에 업로드하여 k8s의 Deployment 오브젝트에서 이미지를 사용할 수 있게 할 것이다.
$ docker login
$ docker image push my-server/swagger-ui:v1.0.0
6. 쿠버네티스에서 Swagger UI 관리하기
6-1. 코드형 인프라 활용
쿠버네티스의 최대 강점은 인프라를 코드로 관리할 수 있다는 것이다.
아래 swagger-ui라는 디렉토리는 실제 swagger-ui 프로젝트의 커스터마이징한 부분만 옮겨 조금씩 수정하며 버저닝할 수 있게 한 것이다. (아니면 Swagger UI 프로젝트를 통째로 레포지토리에 넣어도 괜찮다.)
이 디렉토리에서는 커스터마이징 과정을 설명하는 문서를 꼼꼼하게 작성해두는 것이 좋다.
6-2. Service 구성
Docker를 통해 Swagger UI가 8080 포트에서 실행되므로 , targetPort를 8080 포트로 설정하고 외부에서는 80 포트를 통해 접근할 수 있도록 한다.
apiVersion: v1
kind: Service
metadata:
name: swagger-svc
namespace: swagger
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
selector:
app: swagger-ui
6-3. Deployment 스크립트 구성하기
Deployment 스크립트를 구성하는 것은 다소 복잡할 수 있다. Swagger UI가 Object Storage에 마운트할 수 있도록 설정을 추가해줘야 하기 때문이다. 사실 Docker 컨테이너의 파일 시스템에서는 직접 S3를 마운트해올 수 없으므로, 호스트 시스템의 힘을 빌려야 한다.
5. Docker 이미지 만들기에서는 마치 도커 컨테이너에서 직접 마운트하는 것처럼 그려졌지만 실제로는 아래와 같은 구조로 S3를 마운트하게 된다.
따라서 s3fs를 사용하는 Docker Image를 실행하려면 명령어를 아래와 같이 구성해줘야 한다.
- --rm: 컨테이너가 종료될 때, 연결된 파일시스템을 삭제해주는 옵션이다.
- --device /dev/fuse: Docker 컨테이너가 실행되는 호스트에 FUSE 기반 마운트를 수행할 수 있도록 디바이스 디렉토리를 만든다. 실제로 S3를 마운트할 곳을 지정.
- --cap-add SYS_ADMIN: 파일 시스템 마운트를 컨테이너 내부에서 수행하려면 어드민 권한이 필요한데, 그 권한을 부여하는 옵션이다.
$ docker run \
--rm --device /dev/fuse \
--cap-add SYS_ADMIN \
-p 8080:8080 my-server/swagger-ui:v1.0.0
위 설정을 Deployment에 담으면 된다.
- volumeMounts와 volumes를 설정하여 /dev/fuse에 마운트하도록 설정했다.
- securityContext를 통해 SYS_ADMIN 권한을 부여했다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: swagger-ui-deploy
namespace: swagger
labels:
app: swagger-ui
spec:
replicas: 1
selector:
matchLabels:
app: swagger-ui
template:
metadata:
labels:
app: swagger-ui
spec:
nodeSelector:
type: production
imagePullSecrets:
- name: my-docker-registry # Docker Hub Secret 등록 필요
containers:
- name: swagger-ui
image: my-server/swagger-ui:v1.0.0
securityContext:
privileged: true
capabilities:
add:
- SYS_ADMIN
volumeMounts:
- mountPath: /dev/fuse
name: fuse-device
ports:
- containerPort: 8080
volumes:
- name: fuse-device
hostPath:
path: /dev/fuse
6-4. Swagger UI 주소 은닉하기
쿠버네트스의 Ingress 오브젝트를 사용하여 Swagger UI 서버에 대한 접근을 은닉할 수 있다. 예를 들어, 팀에서 VPN을 사용한다면 해당 VPN에 접속한 유저만 Swagger UI에 접근할 수 있도록 설정할 수 있다.
- nginx.ingress.kubernetes.io/whitelist-source-range라는 어노테이션을 사용하면 해당 인그레스에 접근 가능한 IP를 설정할 수 있다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: swagger-ingress
namespace: swagger
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: 20m
nginx.ingress.kubernetes.io/whitelist-source-range: "허용할IP주소1, 허용할IP주소2"
spec:
ingressClassName: nginx
tls:
- hosts:
- swagger.my-server.com
secretName: my-tls
rules:
- host: swagger.my-server.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: swagger-svc
port:
number: 80
6-5. 스크립트 실행하기
먼저 네임스페이스를 스웨거 자원을 담을 네임스페이스를 생성하자.
$ kubectl create ns swagger
그 후, Docker Hub에 접근하기 위한 Secret을 등록하고, TLS가 있다면 TLS Secret도 등록해준다.
모든 준비가 끝났다면, Deployment로 Pod를 띄우고 Service와 Ingress를 구성한다.
$ kubectl apply -f swagger/swagger-ui-deploy.yaml
$ kubectl apply -f swagger/swagger-ui-service.yaml
$ kubectl apply -f swagger/swagger-ui-ingress.yaml
이제 ingress로 설정한 주소(위의 예시에서는 swagger.my-server.com)에 접근하면 Swagger UI가 정상적으로 실행되는 것을 확인할 수 있다.
정리
Swagger UI를 효과적으로 활용하기 위해 S3 마운트 및 쿠버네티스 환경 구축 등 여러 가지 사안을 고려해야 하지만, 적절한 설정을 통해 안정적이고 확장 가능한 시스템을 구축할 수 있다. Swagger UI 프로젝트를 커스터마이징하고, S3 마운트 작업을 통해 문서를 안전하게 관리할 수 있는 환경을 구축했다. 또한, NGINX를 통해 문서를 호스팅하여 CORS 문제를 해결했다.
Swagger UI와 관련된 인프라가 코드형 인프라로 구축되었기 때문에 추후 시스템 변경이나 업데이트가 용이하며, 팀원들이 더 쉽게 유지보수할 수 있게 되었다.
이제 우리가 Swagger UI를 배포하기 위해 거쳐야 할 일은 3가지다.
- Swagger UI 코드 수정하기
- 도커 이미지 빌드하고 Push하기
- 쿠버네티스 Pod 새로운 버전으로 교체하기
위 과정이 번거롭게 느껴진다면! Jenkins의 파이프라인으로 사용하여 Swagger UI를 배포하는 과정 또한 자동화할 수 있다.
더 나은 방법을 찾기 위해
결론적으로, 위에서 설명한 시스템은 내 상황에 맞게 설계된 해결책이다. Object Storage를 사용하고 마운트를 활용한 접근 방식이 회사의 특정 제약을 해결하는 데 효과적이었지만, 모든 상황에 적합한 정답은 아니다.
각자의 기술 스택과 제약 조건에 따라 Swagger UI와 문서를 어떻게 배포하고 관리할지 고민할 때, 가장 중요한 것은 자신의 상황에 맞는 최선의 선택을 하는 거라고 생각한다.
본 글이 다양한 환경에 맞춰 API 문서를 구축하고 배포하는 데 도움이 되는 참고 자료가 되길 바란다.