들어가며
지난 글에서는 사용자에게 멀티테넌시 Airflow 환경을 제공한 방법을 살펴봤는데요. 이번 글에서는 사용자들이 데이터 파이프라인 개발을 할 수 있도록 격리된 Airflow 환경을 제공하는 방법을 말씀드리고, Airflow를 자동으로 제공할 수 있는 설정까지 자세히 살펴보겠습니다.
Airflow 사용자 테스트 환경을 제공하기 위해서는 아래 3가지 사항을 만족해야 했습니다.
- 사용자들이 데이터 파이프라인 개발을 할 수 있도록 각각 격리된 Airflow 환경을 제공해야 합니다.
- Airflow 웹에 접속할 수 있는 URL을 각 기능별로 제공해야 합니다.
- 사용자 환경은 GitHub의 Action을 통해 자동으로 생성되고 회수돼야 합니다.
그럼 위 사항들을 하나씩 살펴보겠습니다.
Airflow 환경 구성
먼저 사용자들에게 어떤 방식으로 각각 격리된 Airflow 환경을 제공했는지 말씀드리겠습니다.
프로덕션과 개발 환경
각 테넌트의 데이터 파이프라인을 제공하기 위한 Airflow 환경은 다음 그림과 같습니다. 각 테넌트 프로젝트별로 이런 환경을 빠르게 생성할 수 있도록 Helm 차트를 커스터마이징했습니다. 또한 실제 비즈니스 로직 처리는 Airflow 워커가 아니라 KubernetesPodOperator
를 이용해 별도 파드에서 실행된다는 점도 참고하시기 바랍니다. 프로덕션과 개발 환경은 ArgoCD로 배포할 때 항상 고정된 값을 기준으로 각기 다른 파이프라인 환경이 만들어지도록 Helm 차트를 수정했는데요. Helm 차트의 빌트인 객체 가운데 release
를 적극 활용해 테넌트 프로젝트별로 각기 다른 데이터 파이프라인 환경을 구성했습니다.

사용자 테스트 환경
사용자 테스트 환경도 프로덕션 및 개발 환경과 유사한데요. 몇 가지 차이점이 있습니다. 테넌트 파이프라인 프로젝트에 새로운 feature 브랜치가 생성되면 Jenkins에 웹훅으로 테스트 Airflow를 생성해 주는 Jenkins 프로젝트를 실행합니다. 해당 Jenkins 프로젝트는 Helm 차트를 통해 Airflow를 생성하고, Airflow의 Health Check API를 호출해서 사용자 테스트 Airflow 웹 서버에 접근할 수 있는지 확인합니다. 설정 방식을 포함한 자세한 사항은 '사용자 테스트 Airflow 제공 파이프라인 구성 예시' 섹션에서 예시 코드와 함께 자세히 설명하겠습니다.

웹 서버 접근 방식
앞서 잠깐 설명드렸듯이 저희는 각 프로젝트별로 프로덕션과 개발, 사용자 테스트 환경을 각각 준비해서 제공합니다. 이때 사용자 테스트 환경에서는 Git 프로젝트별로 feature 브랜치를 새로 생성할 때마다, 기능을 개발하고 테스트할 수 있는 독립된 환경을 생성해 제공합니다. 이에 따라 프로젝트와 각 환경별로 Airflow 웹 서버 여러 개가 동시에 실행되는데요. 따라서 사용자의 웹 서버 접근 요청을 적절하게 분배해서 올바른 웹 서버로 트래픽을 전송해야 합니다.
아래 그림에서 URL 부분을 보시면 사용자의 요청을 적절하게 분배해 올바른 웹 서버로 전송하기 위해 저희가 정의한 규칙을 확인할 수 있습니다. URL의 호스트 부분에서 어떠한 환경(프로덕션, 개발, 사용자 테스트)에 접근할지가 결정되고, 패스에서 어떤 프로젝트(project_1, project_2)에 접근할지가 결정됩니다.

이와 같은 규칙을 Kubernetes Ingress 리소스의 라우팅 규칙으로 정의해서 Kubernetes 클러스터에 같이 배포합니다. 배포된 각 Ingress 리소스의 라우팅 규칙을 토대로 실제로 트래픽을 나눌 때는 인그레스 컨트롤러(Ingress Controller)를 사용했는데요. 여러 컨트롤러 중에서 다른 프로젝트에서도 활용한 적이 있던 NGINX 인그레스 컨트롤러를 사용했습니다. Kubernetes에서 공식 지원하는 Helm 차트를 활용했으며, 4.0.16 버전을 사용했습니다.
Nginx 인그레스 컨트롤러 구성 및 배포 방식
Nginx 인그레스 컨트롤러 Helm 차트를 그대로 사용할 수는 없었고, 저희의 요구 사항에 맞게 일부 수정할 필요가 있었습니다. 외부에 서비스를 노출하기 위해 NodePort 서비스가 31818 포트로 바인딩하도록 설정했습니다. 또한 각 Airflow 환경은 서로 다른 네임스페이스로 분리되기 때문에 RBAC(rule based access control) 옵션을 활성화해 ClusterRole과 그와 연관된 ServiceAccount, ClusterRoleBinding 리소스가 배포되도록 설정했습니다.
앞서 언급한 리소스들은 Helm 차트 내에 이미 YAML 파일로 정의돼 있고 템플릿을 통해 제어되기 때문에 Values.yaml 파일에서 원하는 값으로 변경해서 쉽게 설정할 수 있습니다.
그 외에 추가로 Airflow Helm 차트도 수정할 필요가 있습니다. 저희는 각 환경별로 Values.yaml 파일을 생성해서 관리하고 있는데요(values-test.yaml, values-dev.yaml, values-prod.yaml). 아래 코드를 예시로 설명하면, values-test.yaml 파일의 .Values.ingress.web.host
의 값은 airflow-test.com
이 되는 반면, values-dev.yaml 파일에서는 동일한 옵션값이 airflow-dev.com
이 됩니다.
{{- define "webLink" }}
{{- if contains "airflow" .Release.Namespace }}
{{- if contains "project_1" .Release.Namespace }}
http://{{- .Values.ingress.web.host}}/project_1
{{- else if contains "project_2" .Release.Namespace }}
http://{{- .Values.ingress.web.host}}/project_2
{{- end }}
{{- end }}
{{- end }}
또한 배포되는 네임스페이스는 배포되는 환경과 프로젝트 이름 등을 토대로 이름이 결정되기 때문에, 위 코드에서 보시는 바와 같이 네임스페이스에서 조건문으로 비교해 동적으로 웹 서버 URL을 정의합니다. 저희가 만든 가상의 URL은 Airflow가 이해할 수 없기 때문에 웹 서버 컨테이너의 환경 변수(AIRFLOW__WEBSERVER__BASE_URL
)에 해당 URL 값을 할당해 웹 서버가 주소를 인지할 수 있도록 설정해 줄 필요가 있습니다.
또한 위 코드의 webLink
와 같이 같은 이름의 변수지만 실행되는 환경에 따라 유동적으로 값이 변경돼야 하는 경우가 또 있는데요. 예를 들면, 로그를 저장할 원격 저장소의 주소라든지, Git 리포지터리 주소 등이 있습니다. 이들은 추가로 커스텀 템플릿 파일에 정의해서 사용하고 있습니다. 각자의 환경에 맞게 변수를 정의해서 사용하시면 될 것 같습니다.
사용자 테스트 Airflow 제공 파이프라인 구성 예시
사용자 테스트 Airflow 제공 파이프라인을 구성하는 예시를 보여드리곘습니다.
- 먼저 GitHub에서 웹훅 토큰을 생성한 뒤 웹훅을 설정합니다.
- GitHub에서 생성한 웹훅 토큰을 Jenkins에 설정합니다.
- 마지막으로 Kubernetes에서 Airflow 사용자 테스트 환경 배포를 자동화합니다.
각 단계를 화면 캡처와 함께 자세히 설명하겠습니다.
GitHub - 웹훅 토큰 생성
아래와 같이 Profile > Settings를 클릭합니다.

Settings > Developer settings > Personal access tokens > Generate new token에서 아래와 같이 설정합니다. 이때 표시되는 토큰은 Jenkins에서 웹훅을 설정할 때 사용해야 하는데요. 이후 다시 볼 수 없으니 주의하시기 바랍니다.

GitHub - 웹훅 설정
Settings > Hooks > Add webhook에서 아래와 같이 Payload URL과 Content type을 설정하고, 트리거를 설정합니다.
Payload URL & Content type 설정
URL: http://${jenkins_url}/generic-webhook-trigger/invoke?token=${token}

트리거 설정
아래와 같이 Airflow 사용자 테스트 환경을 생성하고 회수하기 위한 GitHub Action을 선택합니다.

Jenkins - 웹훅 토큰 설정
Jenkins에서 웹훅 토큰을 설정하기 위해선 먼저 플러그인을 설치해야 합니다.
Jenkins 플러그인 설치
Jenkins 관리 > 플러그인 관리 > 설치 가능 > 플러그인 검색 > 설치에서 Generic Webhook Trigger 플러그인을 설치합니다. 자세한 사항은 Generic Webhook Trigger 페이지를 참고하시기 바랍니다.

GitHub에서 발급받은 토큰 설정
Dashboard > Credentials > System > Global credentials (unrestricted)에서 아래와 같이 Kind와 Secret 항목을 설정합니다.
- Kind: GitHub에서 발급받은 토큰을 텍스트로 설정하기 위해 Secret Text로 설정합니다.
- Secret: GitHub에서 발급받은 토큰을 입력합니다.

Kubernetes - 서비스 어카운트 설정
Airflow 사용자 테스트 환경 배포를 자동화하기 위해 아래와 같이 Kubernetes 서비스 어카운터 토큰 방식으로 Kubernetes 클러스터에 접근합니다.
# Create Service Account
kubectl -n kube-system create serviceaccount root
# Cluster Role Binding 생성
kubectl create clusterrolebinding root-cluster-admin-binding --clusterrole=cluster-admin --serviceaccount=kube-system:root
# ~/.kube/config 설정 추가
TOKENNAME=`kubectl -n kube-system get serviceaccount/root -o jsonpath='{.secrets[0].name}'`
TOKEN=`kubectl -n kube-system get secret $TOKENNAME -o jsonpath='{.data.token}'| base64 --decode`
kubectl config set-credentials root --token=$TOKEN
kubectl config set-context --current --user=root
사용자 테스트 Airflow 생성
이제 어떻게 사용자 테스트 Airflow를 생성하는지 시퀀스 다이어그램과 Jenkins 설정 등을 살펴보겠습니다.
시퀀스 다이어그램

Jenkins 설정
Feature 브랜치를 GitHub origin 브랜치에 푸시한 뒤에 사용자 테스트 환경을 생성하기 위한 Jenkins 설정하는 방법을 살펴보겠습니다.
General 탭
GitHub Payload를 통해 넘어오는 ref 값에서 브랜치 명을 가져오기 위해 아래와 같이 설정합니다.

아래는 GitHub Payload 예시입니다.

Source Code Management 탭
Source Code Management 탭에서 리포지터리를 설정합니다.
- Airflow GitHub에서 Jenknis 서버에서 생성한 SSH로 접근하기 위한 퍼블릭 키를 등록해야 합니다.
- Feature 브랜치일 때만 빌드를 실행하기 위해 Branch Specifier를 설정합니다.

Build Triggers 탭
- Generic Webhook Trigger 체크
- 앞서 설치한 Generic Webhook Trigger 플러그인을 사용하기 위해 아래와 같이 체크합니다.

- Post content parameters 설정
- GitHub Payload를 통해 넘어온 값을 Generic Webhook Trigger 플러그인에서 사용하기 위한 파라미터를 설정합니다.

- Header parameters 설정
- GitHub Payload를 통해 넘어온 값을 Generic Webhook Trigger 플러그인에서 사용하기 위한 설정입니다.
- 사용자 개별 Airflow를 생성하기 위해서 GitHub 이벤트 중 feature 브랜치 푸시 Action만 필터링합니다.

- Optional filter 설정
- Expression에서 정규 표현식을 사용해 feature 브랜치일 때만 빌드를 트리거합니다.

빌드
아래와 같은 쉘 명령어를 실행해 빌드합니다.
echo "GitHub Hook Trigger"
cd ~/$(manifest_git_repo_path}
git pull origin master
echo ${ref}
BRANCH_NAME="${ref}"
# Create kubernetes namespace
kubectl create namespace ${BRANCH_NAME}
# Create User Test Airflow
cd ~/workspace/$(manifest_git_repo_name}/${airflow_helm_chart}
helm install -n ${BRANCH_NAME} -f ./values-alpha.yaml . --generate-name --set dags.gitSync.branch=feature/${BRANCH_NAME} --set airflow.config.AIRFLOW__KUBERNETES__NAMESPACE=${BRANCH_NAME}
# Send Slack Airflow Web URL
CHANNEL="#slack_channel_name"
NICKNAME="jenkins"
EMOJI=":pepe_doflamingo:"
AIRFLOW_WEB_URL="http://${airflow_domain}/feature/$BRANCH_NAME"
AIRFLOW_HEALTH_CHECK_URL="$AIRFLOW_WEB_URL/health"
MSG="Airflow Web URL : $AIRFLOW_WEB_URL"
SLACK_URL=https://${slack_domain}/notice
# If the airflow health check is successful, a notification is sent to slack
STATUS_CODE=`curl -s -o /dev/null -w "%{http_code}" $AIRFLOW_HEALTH_CHECK_URL`
COUNT=1
while [ $STATUS_CODE != "200" ] ; do
if [ $COUNT -gt "120" ] ; then
echo "Failed to start airflow web"
exit 1
fi
echo "Waiting more..."
echo $STATUS_CODE
let COUNT=$COUNT+1
sleep 1
STATUS_CODE=`curl -s -o /dev/null -w "%{http_code}" "$AIRFLOW_HEALTH_CHECK_URL"`
done
# Send Slack
curl -X POST -H "X-SENDER': slack" -d "channel=$CHANNEL" -d "nickname=$NICKNAME" -d "color=good" -d "message=$MSG" -d "icon_emoji=$EMOJI" "$SLACK_URL"
사용자 테스트 Airflow 회수 예시
이제 어떻게 사용자 테스트 Airflow를 제거하는지 시퀀스 다이어그램과 Jenkins 설정 등을 살펴보겠습니다.
시퀀스 다이어그램

Jenkins 설정
사용자 테스트 환경을 회수하기 위한 Jenkins 설정하는 방법을 살펴보겠습니다.
General 탭
GitHub Payload로 넘어오는 action과 ref 값에서 브랜치 명을 가져오기 위해 아래와 같이 파라미터를 설정합니다.

Source Code Management 탭
Source Code Management 탭에서 리포지터리를 설정합니다.
- Airflow GitHub에 Jenknis 서버에서 생성한 SSH로 접근하기 위한 퍼블릭 키를 등록해야 합니다.
- Feature 브랜치일 때만 빌드를 실행하기 위해 Branch Specifier를 설정합니다.

Build Triggers 탭
- Generic Webhook Trigger 체크
- 위에서 설치한 Generic Webhook Trigger 플러그인을 사용하기 위해 아래와 같이 체크합니다.

- Post content parameters 설정
- 아래와 같이 GitHub Payload를 통해 넘어온 값을 Generic Webhook Trigger 플러그인에서 사용하기 위한 파라미터를 설정합니다.


- Header parameters 설정
- GitHub Payload를 통해 넘어온 값을 Generic Webhook Trigger 플러그인에서 사용하기 위한 설정입니다.
- 사용자 개별 Airflow를 회수하기 위해서 GitHub 이벤트 중 Pull Request Action만을 필터링합니다.

- Optional filter 설정
- Expression에서 정규 표현식을 사용해 GitHub Action이 closed인 경우에만 빌드를 트리거합니다.

빌드
아래와 같은 쉘 명령어를 실행해 빌드합니다.
echo "GitHub Hook Trigger"
BRANCH_NAME="${ref}"
echo "${BRANCH_NAME}"
# Delete kubernetes namespace
kubectl delete namespace ${BRANCH_NAME}
마치며
많은 데이터 팀들이 Kubernetes 환경에서 데이터 파이프라인 구축을 진행하고 있거나 구축하기 위해 준비하고 있을 것이라고 생각합니다. 이 글이 그런 분들에게 조금이라도 도움이 되었으면 좋겠습니다. 저희 팀은 데이터와 관련해 많은 도전을 하고 있는데요. 앞으로도 도움이 될 만한 내용으로 꾸준히 찾아뵙겠습니다. 긴 글 읽어주셔서 감사합니다.