티스토리 뷰

728x170

지난 포스팅에서는 Jenkins를 활용한 Kubernetes 자동 빌드 배포 환경 구성 중 Jenkins Pipeline 구성과 Jenkins Credential을 등록하는 과정에 대해 살펴보았습니다.

지난 Jenkins 관련 포스팅은 아래를 참고하시기 바랍니다.
Kubernetes Jenkins - 자동 배포환경 구성 (1/2)


GitLab Project 검토

앞선 포스팅에서와 같이 이번 포스팅에서는 GitLab Project에 구성되어 있는 각종 환경파일과 Kubernetes Deploy를 위한 deployment.yaml 파일에 대해 살펴보도록 하겠습니다.
GitLab Project는 springboot라는 프로젝트로 생성하였으며, 프로젝트 구성은 다음과 같습니다.

 

앞선 포스팅의 Docker build stage에서 사용할 Dockerfile과 wboot.sh 그리고 Maven build를 통해 생성 될 template.war를 위한 src/main 디렉토리 등이 있습니다.
또한 아직 살펴보지 않은 부분이지만, Jenkins Pipeline의 Kubernetes Deploy Stage에서 수행 될 deployment.yaml 파일 역시 GitLab에 업로드 되어 관리되고 있습니다.

# 여기서 주의 사항은 GitLab / Jenkins를 처음 써보시는 분들을 위해 기록합니다. 위와 같은 구성은 모두 본인이 구성하는 것이지 정해져 있는 틀이 있는 것은 아닙니다. Pipeline에 정의하기 위한 디렉토리 구조, 파일 명, 위치 등 모두 본인이 관리하기 편하도록 구성하는 것이지 위와 같이 구성한 것이 답은 아니라는 것입니다. 다만 보다 손쉽게 관리하기 위해서는 SpringFramework or SpringBoot or Egov 등과 같은 표준 템플릿 환경 기반에 특정 디렉토리를 생성하여 Docker / Kubernetes 관련 환경 파일을 관리하는 것이 좋습니다.

이후에 다시 살펴볼 날이 오겠지만, Kubernetes의 경우 Helm이라는 Chart Manager를 통해 Yaml 파일을 관리할 수 있으므로 현 시점에서 GitLab은 Docker build에 필요한 파일들의 관리에 보다 중점을 두는 것이 중요합니다.

1) Dockerfile
Dockerfile은 Docker Image를 생성하기 위한 Docker build Step이 정의되어 있습니다.
제가 작성한 Dockerfile 예시는 다음과 같습니다.

FROM 192.168.56.100:5000/middleware/wildfly/wildfly_base_image:18.0.1.Final

ADD springboot.war /opt/jboss/wildfly/standalone/deployments/
ADD wboot.sh /opt/jboss/wildfly/bin/
#RUN mkdir /opt/jboss/wildfly/standalone/log && touch /opt/jboss/wildfly/standalone/log/standalone.out

EXPOSE 8080
USER jboss
CMD ["/bin/bash", "-x", "/opt/jboss/wildfly/bin/wboot.sh"]

- 192.168.56.100:5000 Nexus Docker Registry에 업로드 되어 있는 wildfly 18.0.1.Final Image를 기반으로 생성합니다.
springboot.war 파일을 wildfly deployment 디렉토리로 복사합니다.
wboot.sh 파일을 bin 디렉토리에 복사합니다.
그리고 8080  포트를 EXPOSE하고 wboot.sh을 실행하는 구조로 Docker 이미지가 생성됩니다.

Dockerfile의 자세한 생성과정을 확인하고 싶으시면, 다음 URL을 참고하도록 합니다.
https://docs.docker.com/engine/reference/builder/

2) wboot.sh

#!/bin/bash
export NODENAME=standalone
export JBOSS_DIR=/opt/jboss/wildfly
export JBOSS_LOG_DIR=$JBOSS_DIR/$NODENAME/log

$JBOSS_DIR/bin/standalone.sh -Dspring.profiles.active=dev -Djboss.server.base.dir=$JBOSS_DIR/$NODENAME \
        -Djboss.node.name=$NODENAME -c standalone-ha.xml -b 0.0.0.0 -bmanagement=0.0.0.0 \
        -Djboss.socket.binding.port-offset=0

위와 같이 WildFly가 Scale-Out 환경에서도 구동 될 수 있도록 Depend 한 설정을 제거한 기동 스크립트입니다.

3) deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wildfly-deployment
  labels:
    app: wildfly
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wildfly
  template:
    metadata:
      labels:
        app: wildfly
    spec:
      containers:
      - name: wildfly
        image: 192.168.56.100:5000/middleware/wildfly/wildfly_custom_image:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
      nodeSelector:
        key: worker
---
apiVersion: v1
kind: Service
metadata:
  name: wildfly
spec:
  selector:
    app: wildfly
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
  type: ClusterIP
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: wildfly-ingress
  namespace: default
spec:
  rules:
  - host: test.nrson.co.kr
    http:
      paths:
      - backend:
          serviceName: wildfly
          servicePort: 8080
        path: /spring

deployment.yaml 파일에는 실제로 kubernetes에 deploy되는 component 들이 정의됩니다.

deployment.yaml에 정의되어 있는 kind는 총 3가지로 Deployment / Service / Ingress 입니다.

Deployment는 Dockerfile로 생성한 이미지를 deploy 하며, Service는 Pod들의 대표 도메인 역할 그리고 Ingress는 Client의 유입점에서 Service와의 연결을 담당합니다.

# 여기서 주의 사항은 GitLab과 연결과 Jenkins는 GitLab으로 부터 Check Out 받은 Source 디렉토리를 다음 경로에 저장합니다. 예를 들어 Jenkins를 기동하는 계정이 root일 경우 다음 경로에 Check Out을 받습니다.

[root@ciserver workspace]# pwd
/root/.jenkins/workspace
[root@ciserver workspace]# ls -la
total 4
drwxr-xr-x.  4 root root   42 Dec 20 20:15 .
drwxr-xr-x. 16 root root 4096 Dec 22 00:17 ..
drwxr-xr-x.  8 root root  277 Dec 21 23:53 Kubepipe
drwxr-xr-x.  2 root root    6 Dec 21 23:53 Kubepipe@tmp
[root@ciserver workspace]#

$HOME/.jenkins 경로가 별도로 지정되지 않았을 경우 jenkins Home Directory이며, workspace 디렉토리 하위에 Check Out 받은 소스 파일들이 다운로드 되어 집니다.


Jenkins Kubernetes Deploy Pipeline

다음으로 Jenkins로 돌아와 Kubernetes Deploy Stage를 살펴보겠습니다.

stage('Kubernetes deploy') {
steps {
kubernetesDeploy configs: "deployment.yaml", kubeconfigId: 'springboot'
sh "kubectl --kubeconfig=/root/.jenkins/.kube/config rollout restart deployment/wildfly-deployment"
}
}

kubernetesDeploy configs에 GitLab에서 작성한 deployment.yaml 파일 이름을 작성하며, kubeconfigId는 Jenkins Credential 작성 시 Kubernetes 용으로 생성한 ID를 입력합니다.

그리고 Rolling Update를 적용하기 위한 rollout 옵션과 함께 wildfly-deployment를 Deploy합니다.


Jenkins Build with Parameters

이제 작성이 완료되었으니 Jenkins로 빌드 및 배포를 진행해 보도록 하겠습니다.

1) Jenkins Project → Build with Parameters

- 위와 같이 Pipeline에서 정의했던 parameter들을 빌드 전에 변경할 수 있습니다.

- 빌드하기 버튼을 선택합니다.

2) 빌드하기

빌드하기 버튼을 클릭하면 위와 같이 Stage 별로 구분되어 Pipeline이 수행되며, 정상적으로 완료되었을 경우 오른쪽 이미지와 같이 성공이 완료됩니다.

상세 로깅은 왼쪽 Build History에 Build Num을 선택하고 Console Output을 클릭하여 보거나, Stage 위에 마우스 커서를 올려 해당 Stage의 로그만 보는 방법 등이 있습니다.


Kubernetes Status 확인

그럼 정상적으로 Deploy가 되었는지 확인해 보도록 하겠습니다.

빌드 전 Service / Deployment / Ingress 정보입니다.

위와 같이 default namespace 정보를 기반으로 빌드 수행 이후의 상태를 확인해 보겠습니다.

Kubernetes에 deployment인 wildfly-deployment, service wildfly, ingress wildfly-ingress, pods wildfly-deployment-864b6f45dc-b5t2b가 deploy 되어 있는 것을 확인할 수 있습니다.

Kubernetes에 deploy된 application에 접근하는 방법은 다양하며, server에서 직접 wildfly ClusterIP로 접근하여 호출해 보도록 하겠습니다.

위와 같이 kubectl get service로 확인 가능한 kubernetes service의 Cluster-IP:PORT (10.233.21.223:8080)으로 호출할 수 있으며, 이는 하위에 Pod들에 자동으로 전달됩니다.

curl -v http://10.233.21.223:8080/hello를 호출해 보니 Hello, Spring Boo App이 정상 호출되는 것을 확인할 수 있습니다.


Ingress Controller

External Client가 ClusterIP에 접근하기 위해서는 Ingress라는 또 다른 서비스가 정의되어야 합니다.

Ingress를 정의하기 위해서는 Ingress Controller와 Ingress Service가 함께 정의되어야 하며, 이를 기반으로 외부에서 유입되는 사용자의 요청을 처리할 수 있습니다.

- Ingress-Controller는 Service가 URI 또는 Domain에 의해 결정되면 이를 Service에 매핑하는 방식해 주는 역할을 담당

- Ingress-Service는 External Client가 Ingress Controller에 접근할 수 있도록 하는 역할을 담당

위 이미지는 Kubernetes에 접근하는 사용자의 접근 방식을 정의한 것입니다.

크게 Application을 호출 하기 위한 단위는 Ingress, Service, Pod로 나누어 설명할 수 있을 것입니다.

- Kubernetes 서비스를 직접 호출할 수 있는 Internal Client의 경우 Hello Service에 정의된 Cluster IP를 활용하여 Service를 호출하고 Service는 서비스에 매핑된 Pod에 Routing & Loadbalancing 하는 역할을 담당합니다.

- Kubernetes 외부에서 호출하는 External Client의 경우 Service에 접근하기 위해 Ingress 라는 또 다른 서비스를 생성해야합니다. Ingress에 정의된 Routing Rule에 접근하기 위해서는 Ingress Contoller에 먼저 접근해야 하는데, Ingress Controller 역시 하나의 Pod 형태로 기동되어 접근을 위한 서비스를 생성해야 합니다.

즉 External Client는 Ingress Controller Service를 통해 Ingress Controller에 접근하여, Client가 요구하는 Ingress로 요청을 전달하고 Ingress는 다시 Service로 Service는 Pod로 요청을 전달하는 방식으로 동작하게 됩니다.

앞선 deployment.yaml 파일에서 정의한 wildfly-ingress가 바로 Hello Ingress / Hi Ingress를 의미하며, 이를 외부 사용자에게 공개하기 위해 우리는 Ingress Controller와 Ingress Service를 생성해 주어야 합니다.

Ingress-Service는 다양한 방법으로 접근 방식을 제공할 수 있으며, External IPs, NodePort, DeamonSet, MetalLB 등으로 제공할 수 있습니다.

ex) nginx-ingress-controller.yaml

apiVersion: v1

kind: Namespace

metadata:

  name: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap

apiVersion: v1

metadata:

  name: nginx-configuration

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap

apiVersion: v1

metadata:

  name: tcp-services

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap

apiVersion: v1

metadata:

  name: udp-services

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

---

apiVersion: v1

kind: ServiceAccount

metadata:

  name: nginx-ingress-serviceaccount

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

---

apiVersion: rbac.authorization.k8s.io/v1beta1

kind: ClusterRole

metadata:

  name: nginx-ingress-clusterrole

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

rules:

  - apiGroups:

      - ""

    resources:

      - configmaps

      - endpoints

      - nodes

      - pods

      - secrets

    verbs:

      - list

      - watch

  - apiGroups:

      - ""

    resources:

      - nodes

    verbs:

      - get

  - apiGroups:

      - ""

    resources:

      - services

    verbs:

      - get

      - list

      - watch

  - apiGroups:

      - ""

    resources:

      - events

    verbs:

      - create

      - patch

  - apiGroups:

      - "extensions"

      - "networking.k8s.io"

    resources:

      - ingresses

    verbs:

      - get

      - list

      - watch

  - apiGroups:

      - "extensions"

      - "networking.k8s.io"

    resources:

      - ingresses/status

    verbs:

      - update

---

apiVersion: rbac.authorization.k8s.io/v1beta1

kind: Role

metadata:

  name: nginx-ingress-role

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

rules:

  - apiGroups:

      - ""

    resources:

      - configmaps

      - pods

      - secrets

      - namespaces

    verbs:

      - get

  - apiGroups:

      - ""

    resources:

      - configmaps

    resourceNames:

      # Defaults to "-"

      # Here: "-"

      # This has to be adapted if you change either parameter

      # when launching the nginx-ingress-controller.

      - "ingress-controller-leader-nginx"

    verbs:

      - get

      - update

  - apiGroups:

      - ""

    resources:

      - configmaps

    verbs:

      - create

  - apiGroups:

      - ""

    resources:

      - endpoints

    verbs:

      - get

---

apiVersion: rbac.authorization.k8s.io/v1beta1

kind: RoleBinding

metadata:

  name: nginx-ingress-role-nisa-binding

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

roleRef:

  apiGroup: rbac.authorization.k8s.io

  kind: Role

  name: nginx-ingress-role

subjects:

  - kind: ServiceAccount

    name: nginx-ingress-serviceaccount

    namespace: ingress-nginx

---

apiVersion: rbac.authorization.k8s.io/v1beta1

kind: ClusterRoleBinding

metadata:

  name: nginx-ingress-clusterrole-nisa-binding

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

roleRef:

  apiGroup: rbac.authorization.k8s.io

  kind: ClusterRole

  name: nginx-ingress-clusterrole

subjects:

  - kind: ServiceAccount

    name: nginx-ingress-serviceaccount

    namespace: ingress-nginx

---

apiVersion: apps/v1

kind: Deployment

metadata:

  name: nginx-ingress-controller

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

spec:

  replicas: 1

  selector:

    matchLabels:

      app.kubernetes.io/name: ingress-nginx

      app.kubernetes.io/part-of: ingress-nginx

  template:

    metadata:

      labels:

        app.kubernetes.io/name: ingress-nginx

        app.kubernetes.io/part-of: ingress-nginx

      annotations:

        prometheus.io/port: "10254"

        prometheus.io/scrape: "true"

    spec:

      # wait up to five minutes for the drain of connections

      terminationGracePeriodSeconds: 300

      serviceAccountName: nginx-ingress-serviceaccount

      nodeSelector:

        key: worker

      containers:

        - name: nginx-ingress-controller

          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.26.1

          args:

            - /nginx-ingress-controller

            - --configmap=$(POD_NAMESPACE)/nginx-configuration

            - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services

            - --udp-services-configmap=$(POD_NAMESPACE)/udp-services

            - --publish-service=$(POD_NAMESPACE)/ingress-nginx

            - --annotations-prefix=nginx.ingress.kubernetes.io

          securityContext:

            allowPrivilegeEscalation: true

            capabilities:

              drop:

                - ALL

              add:

                - NET_BIND_SERVICE

            # www-data -> 33

            runAsUser: 33

          env:

            - name: POD_NAME

              valueFrom:

                fieldRef:

                  fieldPath: metadata.name

            - name: POD_NAMESPACE

              valueFrom:

                fieldRef:

                  fieldPath: metadata.namespace

          ports:

            - name: http

              containerPort: 80

              protocol: TCP

            - name: https

              containerPort: 443

              protocol: TCP

          livenessProbe:

            failureThreshold: 3

            httpGet:

              path: /healthz

              port: 10254

              scheme: HTTP

            initialDelaySeconds: 10

            periodSeconds: 10

            successThreshold: 1

            timeoutSeconds: 10

          readinessProbe:

            failureThreshold: 3

            httpGet:

              path: /healthz

              port: 10254

              scheme: HTTP

            periodSeconds: 10

            successThreshold: 1

            timeoutSeconds: 10

          lifecycle:

            preStop:

              exec:

                command:

                  - /wait-shutdown

---

ex) nginx-ingress-service.yaml

apiVersion: v1

kind: Service

metadata:

  name: ingress-nginx-externalip

  namespace: ingress-nginx

spec:

  ports:

    - name: service

      port: 80

      targetPort: 80

  selector:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

  externalIPs:

  - 192.168.56.102

#  - 192.168.56.103

위 yaml 파일은 externalIPs를 적용하여 클라이언트가 ingress contoller로 접근할 수 있도록 서비스를 등록하는 예시입니다.

nodeSelector를 통해 Ingress Conroller를 배포하고자 하는 node를 선택한 것과 externalIPs로 공개할 외부 ip 주소를 지정한 것 이외에는 별다른 수정 없이 사용 가능합니다.

이후 시간에 Ingress Controller에 대한 다양한 적용 방식과 Cluster IP와의 연결 관계를 파악하기 위한 kube-proxy의 동작 방식을을 함께 자세히 다뤄볼 예정이니 이런 방식으로 동작하다는 정도만 이번 시간에는 알고 넘어 갔으면 합니다.

위와 같이 nginx-controller가 등록이 완료되면, 브라우저에서 다음과 같이 호출할 수 있습니다.

이번 시간에는 Jenkins / GitLab / Nexus / Kubernetes를 활용한 자동 빌드 방식에 대한 마무리와 Ingress Controller에 대해 간단히 살펴보았습니다.

Kubernetes의 추가적인 구성 (pv, pvc, label, nodeselector, hpa 등등)은 다음 포스팅에서 알아보도록 하겠습니다.

그리드형
댓글
댓글쓰기 폼