지난 8주간 진행했던 KANS 스터디의 최종 졸업과제로 "K8s 환경에서 3-tier 구성하기"라는 주제를 선정하였습니다.
해당 주제를 선택한 이유는 최근 참여한 프로젝트에서 비슷한 구조로 설계/도입했던 사례가 있어 공유 및 개선안을 함께 고민하기 위해 선정하였습니다.
필자는 아직까지 다양한 프로젝트 경험은 없지만, 최근 실제 도입된 사례나 요구사항을 보면 아직까지 국내에서는 3-tier 기반의 웹 서비스를 운영하는 고객이 많은 것 같습니다.
최근 MSA 기반의 앱 현대화에 대한 이야기가 많이 나오고 있지만, "잘 동작하는 서비스를 굳이 MSA로 이관해야하나?"라는 질문에 대해서는 선뜻 "네" 라고 대답하기는 어려운 것 같습니다.
현실적으로 많은 업무들이 모놀리식(Monolithic)한 애플리케이션 형태를 가지고 있으며, 앱 리팩토링 과정은 생략하고 클라우드, 클라우드 네이티브 환경으로 넘어가는 경우도 많습니다.
📢 참고 : 결코 권장하는 방식은 NO!
마이그레이션 방법에는 다양한 형태가 있겠지만 Lift&Shift(Rehost) 또는 Replatform 방법이 일반적으로 쉽고 간편하기 떄문에 많이 사용되고 있습니다.
물론, 클라우드 네이티브 환경에 적합(fit)하게 이관하기 위해서는 Application Refactor가 필수적이지만, 컨테이너 환경으로 옮기는 것 만으로도 비용이나 효율성 측면에서 굉장히 큰 장점을 가져갈 수 있다고 생각됩니다.
쿠버네티스 환경에서 3-tier 형태의 애플리케이션을 배포하기 위한 사전 환경 구성입니다.
본 글에서는 Node 2대로 클러스터를 구성하였습니다. 앞단에 Ingress를 통해 유입된된 후 정적 컨텐츠는 apache로 분기하고 동적 컨텐츠는 HA구성된 Wildfly로 전달되도록 구성해보겠습니다.
kube_ping을 알아보기 전에, jgroups에 대해 먼저 알아보자면, JGroups는 멀티캐스트 프로토콜을 사용하여 신뢰성 높은 통신을 할 수 있도록 구현된 통신 라이브러리 입니다. JGroups에서 사용되는 주요한 프로토콜은 다음과 같습니다.
이 중에서 PING이라고 하는 프로토콜은 클러스터의 멤버를 Discovery 할 때 사용하는 프로토콜입니다.
일반적으로는 멀티캐스트 통신을 통해 MPING을 요청하여 리더(코디네이터)를 찾고 다른 멤버를 Discovery 합니다. 하지만 멀티캐스트를 사용할 수 없는 환경(클라우드, K8s의 특정 CNI 등)에서는 다음과 같은 다양한 방식을 사용할 수 있습니다.
KUBE_PING
은 이러한 PING 프로토콜 중 하나이며, Kubernetes에서 클러스터 노드 검색을 위한 프로토콜 입니다. 이러한 KUBE_PING
기능을 활용하여 Wildfly의 Session Cluster를 구성해볼 예정입니다.
💡 참고 : 보다 상세한 설명은 jgroups-kubernetes 문서를 참고
이제 간단하게 KUBE_PING을 사용하여 wildfly의 HA기능을 테스트해보도록 하겠습니다.
참고 : 샘플소스
샘플 소스를 분석해보면 우선 SA(ServiceAccount), CR(ClusterRole), CRB(ClusterRoleBinding) 이 필요합니다.
jgroups-kubeping이 배포될 네임스페이스의 SA에는 pods의 목록을 조회할 수 있는 CR이 필요하기 때문에 해당 권한을 부여해줍니다.
💡 참고 : 네임스페이스 설정시에는
export TARGET_NAMESPACE=hwyu
와 같이 추가할 수 있습니다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: jgroups-kubeping-service-account
namespace: $TARGET_NAMESPACE
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: jgroups-kubeping-pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: jgroups-kubeping-api-access
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: jgroups-kubeping-pod-reader
subjects:
- kind: ServiceAccount
name: jgroups-kubeping-service-account
namespace: $TARGET_NAMESPACE
다음으로 wildfly 앱을 배포할 때 --server-config
에 standalone-ha.xml
을 참조하고 $(POD_IP)
를 변수로 처리하여 실행합니다. 그리고 앞서 생성한 SA를 참조하기 serviceAccountName: jgroups-kubeping-service-account
를 기입합니다.
참고 : 해당 SA를 참조하여야 Cluster Member를 Discovery 할 수 있습니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: wildfly
labels:
app: wildfly
spec:
selector:
matchLabels:
app: wildfly
replicas: 2
template:
metadata:
labels:
app: wildfly
spec:
serviceAccountName: jgroups-kubeping-service-account
containers:
- name: wildfly-kube-ping
image: yuhyungwook/wildfly:25.0.0.Final
command: ["/opt/jboss/wildfly/bin/standalone.sh"]
args: ["--server-config", "standalone-ha.xml", "-b", $(POD_IP), "-bmanagement", $(POD_IP) ,"-bprivate", $(POD_IP) ]
resources:
requests:
memory: 256Mi
imagePullPolicy: Always
ports:
- containerPort: 8080
- containerPort: 7600
env:
- name: POD_IP
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.podIP
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: KUBERNETES_LABELS
value: app=wildfly
wildfly:25.0.0.Final
이미지를 채택하였으며, KUBE_PING
기능을 설정을 위해 config-widlfly.cli
스크립트 파일을 작성하여 추가하였습니다.FROM jboss/wildfly:25.0.0.Final
LABEL MAINTAINER hwyu <hwyu@mantech.co.kr>
ADD config-wildfly.cli /opt/jboss/
ADD cluster.war /opt/jboss/wildfly/standalone/deployments/
RUN /opt/jboss/wildfly/bin/add-user.sh admin accordion --silent \
&& /opt/jboss/wildfly/bin/jboss-cli.sh --file=config-wildfly.cli \
&& rm -Rf /opt/jboss/wildfly/standalone/configuration/standalone_xml_history/*
EXPOSE 8080 9990 7600 8888
sudo docker build -t yuhyungwook/wildfly:25.0.0.Final .
Sending build context to Docker daemon 109.6kB
Step 1/6 : FROM jboss/wildfly:25.0.0.Final
25.0.0.Final: Pulling from jboss/wildfly
f87ff222252e: Pull complete
13776e8da872: Pull complete
0b43aea4eeb1: Pull complete
8116b2f7ca5a: Pull complete
f26d32e28c29: Pull complete
Digest: sha256:35320abafdec6d360559b411aff466514d5741c3c527221445f48246350fdfe5
Status: Downloaded newer image for jboss/wildfly:25.0.0.Final
---> 856694040847
Step 2/6 : LABEL MAINTAINER hwyu <hwyu@mantech.co.kr>
---> Running in 3befa8fb40d7
...(중략)
Successfully built b46ce24831f9
Successfully tagged yuhyungwook/wildfly:25.0.0.Final
sudo docker push yuhyungwook/wildfly:25.0.0.Final
The push refers to repository [docker.io/yuhyungwook/wildfly]
692b28cab699: Pushed
353731747448: Pushed
8255f5f8095c: Pushed
7f40a58d5146: Mounted from jboss/wildfly
869989761eb2: Mounted from jboss/wildfly
3fbe1e874b0d: Mounted from jboss/wildfly
115463be137a: Mounted from jboss/wildfly
613be09ab3c0: Mounted from jboss/wildfly
25.0.0.Final: digest: sha256:e9b2fae230b811795eaa406620adbe185b3f1e5db499f35ab96eae168efb27fa size: 1997
export TARGET_NAMESPACE=hwyu
k apply -f wildfly-sa-role.yaml -n $TARGET_NAMESPACE
k apply -f wildfly-deploy.yaml -n hwyu
deployment.apps/wildfly created
service/wildfly created
kubectl patch svc wildfly -n hwyu -p '{"spec": {"type": "LoadBalancer"}}'
service/wildfly patched
k get svc -n hwyu
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
wildfly LoadBalancer 10.107.77.162 10.20.200.227 8080:32185/TCP 4m23s
이제 HA로 구성된 Wildfly가 정상적으로 클러스터링 되어있는지 확인해봅니다.
두 대의 파드가 정상적으로 Running 인 것으로 확인이 됩니다.
k get pods -o wide -n hwyu
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
wildfly-6878c54b8f-4rfvj 1/1 Running 0 3m50s 172.32.139.178 acc-node1 <none> <none>
wildfly-6878c54b8f-q42xh 1/1 Running 0 3m15s 172.32.24.47 acc-node2 <none> <none>
이제 실제로 클러스터링 된지 확인을 해볼까요?
wildfly 파드의 로그를 확인행보니 정상적으로 Cluster의 Member List가 조회된 것을 확인할 수 있습니다.
(이미지가 실수로 삭제되어 추후 다시 업로드 예정!)
wildfly-6878c54b8f-rdrkp 파드를 삭제하고 세션 정보가 유지되는지 확인해보겠습니다. 기존 배포된 파드를 삭제하고 로그에서 확인해보면 SIGTERM이 수신된 것을 확인할 수 있습니다.
widlfly의 로그를 모니터링 해보니 정상적으로 새로운 POD가 Cluster Member로 추가된 것으로 확인됩니다.
실제 브라우저에서 확인해보니 Session ID는 유지가 되고 Node ID(POD)만 변경된 것을 확인할 수 있습니다.
참고 : 🤦♂️ 그림 속 POD IP -> POD ID 오타입니다!
이렇게 세션 클러스터 기능을 통해 이중화 구성된 Wildfly에서 서로 세션 정보를 공유하고 있는 것이 확인되었습니다!🔥
이번에는 CI/CD 파이프라인을 연계하여 소스/이미지를 빌드(build)하고 배포(deploy)하는 방법을 소개합니다.
📢 참고 : 본 실습에서는 아코디언 v2에서 제공하는 카탈로그 기능을 연계하여 테스트해볼 예정입니다.
1. Wildfly HA 에서는 이미지 빌드시에 사전에 제작된 cluster.war 파일을 ADD
하여 작업하였습니다.
하지만 실제 서비스 환경에서는 빈번하게 소스 변경이 발생하기 때문에 소스의 버전관리와 이에따른 빌드/배포 자동화가 필요합니다.
그렇기 때문에 소스를 빌드하는 CI 단계에서 .war, .jar
와 같은 패키지 파일을 제작하고 이미지 빌드시에 WAS(wildfly, tomcat 등) 베이스 이미지 통합하는 과정을 활용할 수 있습니다.
테스트한 CI/CD 파이프라인 구성도는 다음 두 가지 형태가 있으며, 본 글에서는 두 번째 방식을 사용하여 배포합니다.
방식1. VCS에서 소스 받아서 배포
vcs get -> maven build -> image build -> deploy
플로우 형태로 배포하는 방법
방식2. WAR 패키징된 소스 업로드 방식으로 배포 ✔
war upload -> image build -> deploy
플로우 형태로 배포하는 방법
필자는 편의상 별도 YAML 작성이나 Helm Chart 배포를 사용하지 않고, 아코디언에서 제공하는 Wildfly HA 카탈로그 템플릿을 사용하여 배포하였습니다.
💡 참고 : YAML 작성 내용은 위에서 소개한 샘플과 거의 동일하기 때문에 생략
📢 참고 : 아코디언 카탈로그 기능 소개 영상
쿠버네티스에서는 서비스를 외부에 노출하기 위해 NodePort, LoadBalancer 등을 사용할 수 있지만 Layer 7 동작(Web Proxy)을 컨트롤 하기위해서 Ingress를 별도로 제공하고 있습니다.
위 그림에서 처럼 Ingress를 통해 인입된 트래픽을 경로(Path) 기반으로 분기하여 정적 컨텐츠는 apache로 동적 컨텐츠는 wildfly로 분기하는 환경을 만들어보겠습니다.
Ingress설정은 다음과 같습니다.
/backend
로 호출되도록 path 지정📢 참고 : 이 외의 annotations 설정은 추후 별도로 분석한 글을 작성할 예정!
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hw-ing
namespace: hwyu
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-hash: "sha1"
nginx.ingress.kubernetes.io/session-cookie-name: "route"
spec:
ingressClassName: nginx
rules:
- host: www.hwyu.com
http:
paths:
- pathType: Prefix
path: '/(.+\.(htm|html|bin|zip|cab|exe|js|css|jpeg|jpg|jpe|jfif|pjpeg|pjp|png|bmp|txt|mpeg|mpg|mpe|mpv|vbs|mpegv|avi|shtml|bat|gif|ppt|pdf|swf|ocx)$)'
backend:
service:
name: hw-apache
port:
number: 80
- pathType: Prefix
path: /backend
backend:
service:
name: hw-wildfly-ha
port:
number: 8080
html, css, js 등 각종 정적 컨텐츠는 apache 서버에서 처리할 수 있도록 분기처리 하였습니다. 실제로 apache에 배포한 소스로 접속이 되는지 확인해보겠습니다.
💡 참고 : 실제 운영시에는 구매한 도메인으로 호출가능!
www.hwyu.com
를 호출 화면💡 참고 : 정적 페이지 소스 출처
동적 페이지는 ingress에서 /backed
path로 접속하면 분기되도록 설정하였습니다.
www.hwyu.com/backend
호출한 화면💡 참고 : 이미지를 넣지는 않았지만 HA 구성된 Wildfly 이기 때문에 하나의 wildfly가 내려가더라도 session id는 유지됨!
이렇게 쿠버네티스 환경에서 3-tier 구성하는 방법에 대해서 간략하게 알아보았습니다.
이미지 빌드부터 배포까지 모든것을 다 설명하려고 하다보니 부가적인 설명이나 예시가 부족했던 것 같습니다.
이번 글에서는 전반적인 Overview를 소개하였다면 다음 글에서는 사용된 오픈소스(apache, wildfly 등)와 쿠버네티스에서 사용한 각종 설정 및 ingress 설정 등을 상세하게 파헤쳐보려 합니다.
긴글 읽어주셔서 감사드립니다🙏
부족한 내용이나 잘못된 부분에 대한 피드백은 언제나 환영합니다!🔥🔥
아코디언은 맨텍에서 개발한 관리형 쿠버네티스 또는 PaaS 플랫폼으로 소개드릴 수 있을 것 같습니다.
최근에는 많은 기업, 고객들이 컨테이너 가상화, 쿠버네티스에 대한 도입을 고려하고 있습니다. 하지만 결코 쉽지만은 않기 때문에 관리형 쿠버네티스, 플랫폼 형태의 서비스를 많이 사용하고 있는 추세입니다.
실제로 CNCF 2021 연간 서베이 결과에서 "응답자의 약 79%가 관리형 쿠버네티스를 사용하고 있다"라고 조사될 만큼 관리형 쿠버네티스의 비중이 높습니다.
출처 : CNCF Annual Survey 2021
국내에서도 많은 기업 및 제품들이 CNCF 인증받은 관리형 쿠버네티스 제품을 제공하고 있습니다. (국내는 아코디언이 최고) 참고
혹여나 쿠버네티스를 업무에 도입하려고 고민하시는 분들은 글로벌 CSP에서 제공하는 관리형 쿠버네티스 외에도 다양한 관리형 쿠버네티스에 대한 특/장점을 비교하고 도입해보는 것도 좋을 것 같습니다!