Kubernetes

멀티모듈 빌딩 환경에서 Jenkins -> K8s 빌딩

Jungsoomin :) 2021. 5. 12. 20:30

목차

  • 원격 저장소에 멀티모듈 빌드 방식으로 프로젝트를 잡아놓고, Dockerfile , K8s Manifest 작성 및 버전관리
  • Jenkins 빌드 라인
  • 이후 통신 과정 요약

 

이유

커다란 뜻은 아니고, 좋은 기회가 있어, 개별 소스 관리 방식에서 멀티모듈의 부모 & 자식 관계의 프로젝트에 K8s 빌드라인까지 샘플링할 기회가 생겼다.

 


 

프로젝트 구조 및, 템플릿

  • 젠킨스 빌드 파이프 라인 까지 세세하게 적지는 않음.
  • YAML 템플릿 , setting & build.gradle 파일 정도 정리

일단 프로젝트 구조는 이러하다, 멀티모듈 방식이며, 부모 모듈에서 하위로 중복 디펜던시를 뿌리고, 관리하도록 했다.

멀티모듈 방식

간단한 샘플이므로, 해당 프로젝트의 build.gradle 은 모두 비어있는 파일이다.
부모 모듈 setting.gradle
rootProject.name = 'test'
include ':admin-service'
include ':kpi-service'
부모 모듈 build.gradle , 부트 어플리케이션으므로 bootJar 사이클 기입
buildscript {
    ext {
        springBootVersion = '2.4.5'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath "io.spring.gradle:dependency-management-plugin:1.0.11.RELEASE"
    }
}

subprojects {
    apply plugin: 'java'
    apply plugin: 'org.springframework.boot'
    apply plugin: 'io.spring.dependency-management'

    group = 'com.test.sample'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = 1.8

    repositories {
        mavenCentral()
    }

    configurations {
        compileOnly {
            extendsFrom annotationProcessor
        }
    }

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-actuator'
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
        implementation 'org.springframework.boot:spring-boot-starter-web'
        //implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.4'
        compileOnly 'org.projectlombok:lombok'
        runtimeOnly 'com.h2database:h2'
        runtimeOnly 'org.postgresql:postgresql'
        annotationProcessor 'org.projectlombok:lombok'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
    }

    test {
        useJUnitPlatform()
    }
}

project(":admin-service") {
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-security'
        testImplementation 'org.springframework.security:spring-security-test'
    }

    bootJar{
        enabled(true)
    }
}

project(":kpi-service"){
    ext {
        set('springCloudVersion', "2020.0.2")
    }

    dependencyManagement {
        imports {
            mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
        }
    }
    dependencies {
        implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
    }

    bootJar{
        enabled(true)
    }
}
Admin-Service 의 application.yaml
spring:
  application:
    name: admin-service
  datasource:
    url: jdbc:postgresql://localhost:5432/postgres?currentSchema=public
    username: postgres
    password: sample

  jpa:
    properties:
      hibernate:
        show_sql: true
        format_sql: true
    generate-ddl: true
    hibernate:
      ddl-auto: create-drop
    open-in-view: true

logging:
  level:
    org.hibernate.type.descriptor.sql: trace

server:
  port: 8080

management:
  endpoints:
    web:
      exposure:
        include:
          - info
          - health
  endpoint:
    health:
      show-details: always
---
spring:
  config:
    activate:
      on-profile: k8s
  datasource:
    url: jdbc:postgresql://aws/database?currentSchema=schema
    username: test
    password: sample

management:
  endpoint:
    health:
      show-details: always
Dockerfile , CPU & Memory 준수 이슈로 12.0.2 사용 [ 추후 Kubenetes 에서 Pod.spec.containers[].resources.requests & limits 관리에 문제가 생기면 안 됨], 도커 이미지 실행 문에 변수 넣어 프로파일 변경
FROM openjdk:12.0.2

EXPOSE 8080

ADD ./build/libs/*.jar app.jar

ENTRYPOINT ["java","-jar","-Dspring.profiles.active=k8s","/app.jar"]
Kubernetes 자원 템플릿, Actuator 의 Detail 설정을 잡아놔서 health 를 호출하면 DB 핑도 같이 찍힌다.
apiVersion: v1
kind: Service
metadata:
  name: admin
  namespace: sample
spec:
  type: ClusterIP
  selector:
    app: admin-service
    proj: sample
    type: dev
  ports:
    - port: 8080
      targetPort: 8080
      protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: admin
  namespace: sample
  annotations:
    type: dev
    function: admin-service
    kubernetes.io/change-cause: jenkins build
  labels:
    app: admin-service
    proj: sample
    type: dev
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
  replicas: 2
  selector:
    matchLabels:
      app: admin-service
      proj: sample
      type: dev
  template:
    metadata:
      name: admin
      namespace: sample
      labels:
        app: admin-service
        proj: sample
        type: dev
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: proj
                    operator: In
                    values:
                      - "sample"
                  - key: app
                    operator: In
                    values:
                      - "admin-service"
              topologyKey: "kubernetes.io/hostname"
      restartPolicy: Always
      terminationGracePeriodSeconds: 10
      imagePullSecrets:
        - name: sample-docker-regsitry
      containers:
        - name: admin
          image: privateRepo/sample-admin-service:latest
          imagePullPolicy: Always
          livenessProbe:
            httpGet:
              port: 8080
              path: /api/v1/liveness
            failureThreshold: 4
            initialDelaySeconds: 7
            periodSeconds: 5
            timeoutSeconds: 15
          readinessProbe:
            httpGet:
              port: 8080
              path: /actuator/health
            failureThreshold: 4
            initialDelaySeconds: 10
            periodSeconds: 5
            timeoutSeconds: 15
          ports:
            - containerPort: 8080

젠킨스 모듈 빌딩

  • 깃 체크아웃
  • 원하는 그레들 사이클 
  • Docker 작업
  • K8s 자원 생성
  • 결과 확인 
sudo chmod +x ./gradlew

gradlew clean -p ./admin-service

gradlew build -p ./admin-service

...

 

추후 통신과정

  • Ingress 자원은 클라우드 서비스든 온프레미스 환경의 Cluster 든 도메인을 연결했을것이다.
    • Ingress.rules[].http & host
      • paths[].path.backend.service.name&port ..
공식문서 템플릿
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80
  • 인그레스 컨트롤러가 클라우스 서비스에서 사용하는 LB 를 사용해서 개별 LB 에서 Nginx 를 타고 들어오거나 Ingress Controller 자체가 Nginx 거나 (온프레미스..) 이므로 통해서 진입한다.
별도의 Namespace 를 Cluster 에서 사용 중이니, Servicename.namespacename.svc.cluster.local 로 접근하거나, ExternalName 으로 거쳐 들어가는 방법이 있을 것이다. 샘플링은 Nginx 에서 리버스 프록싱할 때 이미 다른 네임스페이스의 Gateway Svc 를 호출하고 있는 바이다. [KubeProxy 가 모니터링하여 생성한 IpTables 에서 Service 이름에 맞는 Pod 의 EndPoint 를 찾아가고 있다고 보면 된다.] 하나는 Cluster 내부 DNS 에서 이름주는 방식을 이용한 방법이고, 하나는 Service 의 외부 Domain 을 리턴하는 ExternalName 의 특성을 이용한 방법.
  • Nginx 의 Reverse Proxy 를 타고 들어와 SpringCloud Gateway 에서 Predicate 에 잡혀 uri 을 통해 쏘게 된다.
샘플 자원인 Gateway 의 application.yaml
spring:
  application:
    name: sample-gateway
  cloud:
    gateway:
      routes:
        - id: service-a
          uri: http://localhost:8081/
          predicates:
            - Path=/first-service/**

server:
  port: 8080

management:
  endpoints:
    web:
      exposure:
        include:
          - gateway
          - health
    gateway:
      enable: true



---
spring:
  config:
    activate:
      on-profile: k8s

  cloud:
    gateway:
      routes:
        - id: sample-service
          uri: http://admin:8080/
          predicates:
            - Path=/api/versionNumber/admin/**
          filters:
            - RewritePath=/api/versionNumber/admin/(?<path>.*), /api/versionNumber/$\{path}

 

 

외부 -> K8s Cluster Ingress Controller -> Nginx Svc [리버스 프록싱] -> Gateway Pod [ 정확히는 Iptables 에 연결된 Svc 의 Pod 엔드포인트] -> 포워딩 -> Sample App