Skip to content

Latest commit

 

History

History
3033 lines (2158 loc) · 83.4 KB

homework.md

File metadata and controls

3033 lines (2158 loc) · 83.4 KB

과제

매 주 마다 과제는 아래와 같다.

  1. 1주차
    • Nexus private registry를 사용하여 Jenkins Pipeline 구성
    • 컨테이너 Mysql DB 활용
    • Portainer 설치
    • Harbor 구성해 보기
  2. 2주차 : CKA 문제 풀어 보기 문제
  3. 3주차 : kubernetes에 Jenkins Master/Slave 구성하기 (CI - Python )
  4. 4주차 : kubernetes에 CI/CD 구성 (CI : Maven/Skaffold/SonarQube , CD : ArgoCD/kustomize )

1주차


Nexus private registry를 사용하여 Jenkins Pipeline 구성


문제 : nexus (또는 Harabor) 로 구성된 private docker registry를 사용하여 pipeline을 신규로 구성한다.


순서

  • credential을 private docker registry 용으로 신규로 구성 한다.
  • New Item으로 신규 pipeline을 구성한다. ( copy from 이름 활용 )
  • Jenkins 화일을 신규 생성하고 docker registry 관련 값을 수정한다.
  • pipeline 을 실행시키고 본인의 nexus에 도커 이미지가 저장되었는지 확인한다.
  • 참고 : Jenkins_private 화일

답안


  • jenkins에서 본인 nexus (또는 Harbor) 의 docker ci를 신규 생성한다.
  • jenkinsfile을 새로 만들고 수정한다.
    • dockerRepo 변경
    • dockerCredentials 은 신규 생성한 docker ci
  • docker.withRegistry 의 공백에 본인이 nexus (또는 Harbor) 주소를 입력한다.
  • jenkins pipeline 을 copy from 으로 신규 생성하고 Jenkinsfile 이름을 변경한다.

properties([pipelineTriggers([githubPush()])])

pipeline {
    environment {
        // Global 변수 선언
        // Harbor 사용시 : 앞에 project 이름이 붙는다. 예) edu
        dockerRepo = "edu/edu1"
        // nexus
        //dockerRepo = "edu1"
        dockerCredentials = 'edu_private_docker_ci'
        dockerImageVersioned = ""
        dockerImageLatest = ""
    }

    agent any

    stages {
        /* checkout repo */
        stage('Checkout SCM') {
            steps{
                script{
                    checkout scm
                 }
            }   
        }
        
        stage("Building docker image"){
            steps{
                script{
                    dockerImageVersioned = docker.build dockerRepo //+ ":$BUILD_NUMBER"
                    dockerImageLatest = docker.build dockerRepo + ":latest"
                }
            }
        }
        stage("Pushing image to registry"){
            steps{
                script{
                    // if you want to use custom registry, use the first argument, which is blank in this case
                    // Harbor
                    docker.withRegistry('https://211.252.85.148:40002', dockerCredentials){
                    // nexus
                    //docker.withRegistry( 'http://211.252.85.148:40010', dockerCredentials){
                        dockerImageVersioned.push()
                        dockerImageLatest.push()
                    }
                }
            }
        }
        stage('Cleaning up') {
            steps {
                sh "docker rmi $dockerRepo"//:$BUILD_NUMBER"
            }
        }
    }

    /* Cleanup workspace */
    post {
       always {
           deleteDir()
       }
   }
}

컨테이너 Mysql DB 활용


docker compose로 구성한 mysql container에 접속하여 로그인 한 후 wordpress db에 customer 테이블을 생성해 본다.

  • docker-compose.yml 화일 참고
  • table 이름은 customer 이고 필드는 customer_id , customer_name 만 필요

답안


컨테이너로 접속한다.


root@newedu:~/edu1 # docker exec -it ef9757c00f48 sh

컨테이너안에서 명령어로 mysql에 접속한다.
id/패스워드는 docker-compose.yml에서 확인 가능


sh-4.2# mysql -u wordpress -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.41 MySQL Community Server (GPL)

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use wordpress;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

databases 목록을 확인하고 원하는 db를 선택하고
create table 문을 실행한다.


mysql> create table customer ( customer_id varchar(10), customer_name varchar(20));
Query OK, 0 rows affected (0.01 sec)

테이블 목록을 확인하면 customer 테이블이 생성된 걸 확인 할수 있다.


mysql> show tables;
+-----------------------+
| Tables_in_wordpress   |
+-----------------------+
| customer              |
| test                  |
| wp_commentmeta        |
| wp_comments           |
| wp_links              |
| wp_options            |
| wp_postmeta           |
| wp_posts              |
| wp_term_relationships |
| wp_term_taxonomy      |
| wp_termmeta           |
| wp_terms              |
| wp_usermeta           |
| wp_users              |
+-----------------------+
14 rows in set (0.00 sec)

mysql> select * from customer;
Empty set (0.00 sec)

Portainer 설치


문제


docker 컨테이너 GUI 관리 툴인 portainer를 설치하고 웹에서 접속하여 모니터링한다.


답안


데이터를 저장하기 위해 도커 볼륨을 생성한다. 향후에 컨테이너의 폴더와 연결한다.

docker volume create portainer_data

로컬에 도커 볼륨이 생성되어 있는지 확인한다.

docker volume ls

https 포트인 9443만 40005로 변경한다.


docker run -d -p 8000:8000 -p 40005:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:2.11.1



Getting Start를 선택하고 로컬 도커를 클릭한다.


도커에서 관리하는 리소스 대쉬보드를 볼수 있다.


컨테이너 항목을 선택하면 자세한 컨테이너 현황을 볼수 있다.


도커 볼륨은 아래 명령어로 삭제 할 수 있다.

docker volume prune

Harbor 구성해 보기


문제

Harbor 설치 ( https가 힘든 사람은 http 로 구성 )

  • Private Docker Registry 를 Harbor를 사용하여 구성 한다.
  • Harbor 포트는 40002를 사용하며 https 연결하기 위한 인증서 설정을 한다.
  • Harbor 에 edu 프로젝트를 생성하고 신규 계정을 생성하여 members에 추가한다.
  • nginx 이미지를 본인의 Private Docker Registry에 Push 한다.
  • Harbor에서 확인한다.
  • clair 를 사용하여 도커 취약점을 분석한다.

샘플 : https://211.252.85.148:40002/
계정 : admin/New1234!


답안


인증기관 인증서 생성


참고:


먼저 certs 폴더를 생성합니다.


mkdir -p ~/certs
cd ~/certs

CA 인증서를 생성한다. (private key를 생성)


root@newedu:~/certs# openssl genrsa -out ca.key 4096
Generating RSA private key, 4096 bit long modulus
.....++
..................................++
e is 65537 (0x10001)

CA 인증서 private key에서 public key를 추출한다.
private key 파일에는 private key와 public key가 모두 포함되어 있다.


Can't load /root/.rnd into RNG
140457750159808:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Generating a RSA private key
...............................................................................................................................................................................................................................................................................................................................++++
...++++
writing new private key to 'ca.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:KR
State or Province Name (full name) [Some-State]:KyunggiDo
Locality Name (eg, city) []:SeongNam
Organization Name (eg, company) [Internet Widgits Pty Ltd]:KT
Organizational Unit Name (eg, section) []:edu
Common Name (e.g. server FQDN or YOUR name) []:211.252.85.148
Email Address []:shclub@gmail.com

서버 인증서 생성


개인키를 생성합니다.


root@newedu:~/certs# openssl genrsa -out harbor.key 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
............................++++
....................................................................................................++++
e is 65537 (0x010001)

서버 인증서 private key에서 public key를 추출한다.

인증서 서명 요청(CSR)을 생성


root@newedu:~/certs# openssl req -sha512 -new  -key harbor.key  -out harbor.csr
Can't load /root/.rnd into RNG
140445988479424:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:KR
State or Province Name (full name) [Some-State]:KyunggiDo
Locality Name (eg, city) []:SeongNam
Organization Name (eg, company) [Internet Widgits Pty Ltd]:KT
Organizational Unit Name (eg, section) []:edu
Common Name (e.g. server FQDN or YOUR name) []:211.252.85.148
Email Address []:shclub@gmail.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:9302390
An optional company name []:KT

x509 v3 extension file을 생성한다.


cat > v3.ext <<-EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1=211.252.85.148
DNS.2=harbor
EOF

v3.ext 파일을 사용해 harbor 호스트에 대한 인증서를 생성한다.


openssl x509 -req -sha512 -days 3650 \
    -extfile v3.ext \
    -CA ca.crt -CAkey ca.key -CAcreateserial \
    -in harbor.csr \
    -out harbor.crt

아래와 같이 결과가 나오면 된다.


Signature ok
subject=C = KR, ST = KyunggiDo, L = SeongNam, O = KT, OU = edu, CN = 211.252.85.148, emailAddress = shclub@gmail.com
Getting CA Private Key

아래와 같이 화일이 생성이 된 것을 확인 할 수 있다.


root@newedu:~/certs# ll
total 36
drwxr-xr-x  2 root root 4096 Mar  7 00:32 ./
drwx------ 11 root root 4096 Mar  7 00:29 ../
-rw-r--r--  1 root root 2163 Mar  7 00:23 ca.crt
-rw-------  1 root root 3272 Mar  7 00:22 ca.key
-rw-r--r--  1 root root   41 Mar  7 00:32 ca.srl
-rw-r--r--  1 root root 2224 Mar  7 00:32 harbor.crt
-rw-r--r--  1 root root 1821 Mar  7 00:27 harbor.csr
-rw-------  1 root root 3243 Mar  7 00:26 harbor.key
-rw-r--r--  1 root root  259 Mar  7 00:31 v3.ext

Harbor 및 docker에 인증서를 제공


서버 인증서와 키를 Harbor 호스트의 certficates 폴더에 복사한다.


mkdir -p /data/cert
cp harbor.crt /data/cert/
cp harbor.key /data/cert/

docker에서 사용하기 위해 crt 파일을 cert 파일로 변환한다.
docker는 .crt 파일을 CA 인증서로 해석하고, .cert 파일을 클라이언트 인증서로 해석


# /root/certs 에서 진행한다.
openssl x509 -inform PEM -in harbor.crt -out harbor.cert

적절한 폴더를 만든 후 서버 인증서, 키 및 CA 파일을 harbor 호스트의 Docker 인증서 폴더에 복사한다.

만약 nginx 기본 포트 443을 이미 다른 서비스에서 사용하고 있는 경우 /etc/docker/certs.d/yourdomain.com:port를 생성하거나 /etc/docker/certs.d/harbor_IP:port 생성


mkdir -p  /etc/docker/certs.d/211.252.85.148:40002  
cp harbor.cert /etc/docker/certs.d/211.252.85.148:40002/  
cp harbor.key /etc/docker/certs.d/211.252.85.148:40002/  
cp ca.crt /etc/docker/certs.d/211.252.85.148:40002/  

Insecure Registry 설정


우리가 구축한 docker registry는 http로 설정이 되어 있지만 docker client에서는 https로 연결을 시도한다.

정상적으로 remote 에서 연결 하기 위해서는 insecure registry를 설정해야 한다.


linux인 경우 /etc/docker/daemon.json 화일을 vi 에디터로 열고 아래와 같이 추가한다.
docker registry의 ip와 포트를 입력한다.


root@newedu:~# vi /etc/docker/daemon.json
{
  "insecure-registries": [
    "211.252.85.148:40002"
  ]
}

이제 도커를 재시작 합니다.


systemctl restart docker

Harbor 설치 와 설정을 한다.


harbor 설치 파일을 다운로드한다. 먼저 root 폴더로 이동한다.


cd ~/ 
wget "https://github.com/goharbor/harbor/releases/download/v2.1.3/harbor-offline-installer-v2.1.3.tgz"
tar xfvz harbor-offline-installer-v2.1.3.tgz
cd ~/harbor
cp harbor.yml.tmpl harbor.yml

harbor.yml 파일을 수정


hostname: 211.252.85.148

https:
  # https port for harbor, default is 443
  port: 40002 
  # The path of cert and key files for nginx
  certificate: /data/cert/harbor.crt
  private_key: /data/cert/harbor.key
  ...
harbor_admin_password: New1234!

# Harbor DB configuration
database:
  # The password for the root user of Harbor DB. Change this before any production use.
  password: New1234!
...    

Harbor 설치 스크립트 실행


root@newedu:~/harbor# ./prepare
prepare base dir is set to /root/harbor
Unable to find image 'goharbor/prepare:v2.1.3' locally
v2.1.3: Pulling from goharbor/prepare
522a057091dd: Pull complete
afa60fdd2728: Pull complete
bb4d1e42614d: Pull complete
738d7c5d7e5b: Pull complete
0f80235190d5: Pull complete
d807fa4eab15: Pull complete
302f62788a49: Pull complete
472c667560f3: Pull complete
Digest: sha256:0877d0b90addbe605d2dc1b870510c820b428d7fc1ae980d6ce899c4970e0b25
Status: Downloaded newer image for goharbor/prepare:v2.1.3
Generated configuration file: /config/portal/nginx.conf
Generated configuration file: /config/log/logrotate.conf
Generated configuration file: /config/log/rsyslog_docker.conf
Generated configuration file: /config/nginx/nginx.conf
Generated configuration file: /config/core/env
Generated configuration file: /config/core/app.conf
Generated configuration file: /config/registry/config.yml
Generated configuration file: /config/registryctl/env
Generated configuration file: /config/registryctl/config.yml
Generated configuration file: /config/db/env
Generated configuration file: /config/jobservice/env
Generated configuration file: /config/jobservice/config.yml
Generated and saved secret to file: /data/secret/keys/secretkey
Successfully called func: create_root_cert
Generated configuration file: /compose_location/docker-compose.yml
Clean up the input dir

설치를 아래 옵션과 같이 진행힌다.

  • clair : 도커 이미지 취약점 분석도구
  • chartmuseum : helm chart repository

install 을 하면 에러가 발생한다.
아래에서 에러 조치를 한후 다시 실행한다.


root@newedu:~/harbor# ./install.sh --with-clair --with-chartmuseum

[Step 0]: checking if docker is installed ...

Note: docker version: 23.0.1

[Step 1]: checking docker-compose is installed ...

Note: docker-compose version: 1.21.2

[Step 2]: loading Harbor images ...
72021dc640d8: Loading layer [==================================================>]  34.51MB/34.51MB
ac18f6a923ed: Loading layer [==================================================>]  6.237MB/6.237MB
2139b6d15c37: Loading layer [==================================================>]  13.83MB/13.83MB
Loaded image: goharbor/clair-adapter-photon:v2.1.3


[Step 3]: preparing environment ...

[Step 4]: preparing harbor configs ...
prepare base dir is set to /root/harbor
Clearing the configuration file: /config/registryctl/env
Clearing the configuration file: /config/registryctl/config.yml
Clearing the configuration file: /config/nginx/nginx.conf
loaded secret from file: /data/secret/keys/secretkey
Generated configuration file: /compose_location/docker-compose.yml
Clean up the input dir


[Step 5]: starting Harbor ...
Creating network "harbor_harbor" with the default driver
Creating network "harbor_harbor-clair" with the default driver
Creating network "harbor_harbor-chartmuseum" with the default driver
Creating harbor-log ... done
Creating harbor-portal     ... done
Creating redis         ... done
Creating registryctl       ... done
Creating harbor-db     ... done
Creating registry      ... done
Creating harbor-core   ... done
Creating harbor-jobservice ... done
Creating nginx             ... done
✔ ----Harbor has been installed and started successfully.----

현재 설치된 docker-compose의 버전이 낮아 1.18+ 이상 버전이 설치가 되어야 한다.

docker compose를 제거하고 아래와 같이 설치한다.


root@newedu:~/harbor# apt-get remove docker-compose
root@newedu:~/harbor# curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-`uname -s`-`uname -m` -o /usr/bin/docker-compose
root@newedu:~/harbor# chmod +x /usr/bin/docker-compose

docker compose 버전을 확인하고 1.21.2 버전이 설치 된 것을 확인 할 수 있다.


root@newedu:~/harbor# docker-compose version
docker-compose version 1.21.2, build a133471
docker-py version: 3.3.0
CPython version: 3.6.5
OpenSSL version: OpenSSL 1.0.1t  3 May 2016

설치가 완료 되면 아래 명령어로 컨테이너 상태를 확인한다.


root@newedu:~/harbor# docker-compose ps
      Name                 Command             State               Ports
--------------------------------------------------------------------------------
chartmuseum         ./docker-               Up (healthy)
                    entrypoint.sh
clair               ./docker-               Up (healthy)
                    entrypoint.sh
clair-adapter       /home/clair-            Up (healthy)
                    adapter/entryp ...
harbor-core         /harbor/entrypoint.sh   Up (healthy)
harbor-db           /docker-entrypoint.sh   Up (healthy)
harbor-jobservice   /harbor/entrypoint.sh   Up (healthy)
harbor-log          /bin/sh -c              Up (healthy)   127.0.0.1:1514->10514
                    /usr/local/bin/ ...                    /tcp
harbor-portal       nginx -g daemon off;    Up (healthy)
nginx               nginx -g daemon off;    Up (healthy)   0.0.0.0:80->8080/tcp,
                                                           :::80->8080/tcp, 0.0.
                                                           0.0:40002->8443/tcp,:
                                                           ::40002->8443/tcp
redis               redis-server            Up (healthy)
                    /etc/redis.conf
registry            /home/harbor/entrypoi   Up (healthy)
                    nt.sh
registryctl         /home/harbor/start.sh   Up (healthy)

web ui 접속


웹브라우저에서 https://211.252.85.148:40002/ 로 접속한다.


https로 만 연결이 가능하며 Not secure 라고 되어 있는 것은 공인된 CA가 아니기 때문이고 사용하는데는 문제가 없다.



정상적으로 로그인이 되면 아래와 같은 화면이 나온다.



harbor.yml 에서 설정한 admin 계정과 비밀번호로 로그인 한다.



project에 보면 library라는 기본 프로젝트가 있다.
우리는 edu라는 이름의 신규 프로젝트를 구성한다.



docker registry에 접속한 User를 생성한다.


앞에서 생성한 프로젝트인 edu를 선택하고 members tab으로 이동한다.
member 를 추가하고 권한을 부여한다.


이제 Harbor 설정은 완료 되었다.


remote 에서 이미지 push 하기.


push 할 서버의 docker 에서 해당 ip와 포트를 insecure registry 에 추가하고 docker를 재기동 한다.


먼저 private docker registry에 로그인 한다.


jakelee@jake-MacBookAir Downloads % docker login 211.252.85.148:40002
Username: edu
Password:
Login Succeeded

tagging을 한다.


jakelee@jake-MacBookAir Downloads % docker tag nginx 211.252.85.148:40002/edu/nginx

이미지를 push 한다.


jakelee@jake-MacBookAir Downloads % docker push  211.252.85.148:40002/edu/nginx
Using default tag: latest
The push refers to repository [211.252.85.148:40002/edu/nginx]
2221dc0783f7: Pushed
9b5201f20365: Pushed
8382eef9f1dd: Pushed
4b88900a24d4: Pushed
aae0e4f98e7e: Pushed
53f02138e713: Pushed
latest: digest: sha256:e62e73dd7f24578c82f40f15b3e6c40b49e33ccb86188f56472fd27b0621ec37 size: 1570

정상적으로 push 가 되면 Harbor web 에서 확인한다.


clair 설치를 하면 SCAN 버튼이 활성화 되고 docker image의 취약점을 inspect 할 수 있다.



Harbor 종료 및 재기동


harbor 는 docker-compose 로 구성되어 있다.

종료를 위해서는 harbor 폴더로 이동하고 아래 명령어를 사용한다.


root@newedu:~# cd ~/harbor
root@newedu:~/harbor# docker-compose down -v
Stopping harbor-jobservice ... done
Stopping nginx             ... done
Stopping harbor-core       ... done
Stopping clair-adapter     ... done
Stopping clair             ... done
Stopping chartmuseum       ... done
Stopping registry          ... done
Stopping harbor-db         ... done
Stopping redis             ... done
Stopping registryctl       ... done
Stopping harbor-portal     ... done
Stopping harbor-log        ... done
Removing harbor-jobservice ... done
Removing nginx             ... done
Removing harbor-core       ... done
Removing clair-adapter     ... done
Removing clair             ... done
Removing chartmuseum       ... done
Removing registry          ... done
Removing harbor-db         ... done
Removing redis             ... done
Removing registryctl       ... done
Removing harbor-portal     ... done
Removing harbor-log        ... done
Removing network harbor_harbor
Removing network harbor_harbor-clair
Removing network harbor_harbor-chartmuseum

재기동 하기 위해서는 아래와 같이 수행한다.


root@newedu:~/harbor# docker-compose up -d
Creating network "harbor_harbor" with the default driver
Creating network "harbor_harbor-clair" with the default driver
Creating network "harbor_harbor-chartmuseum" with the default driver
Creating harbor-log ... done
Creating registry      ... done
Creating harbor-portal ... done
Creating chartmuseum   ... done
Creating redis         ... done
Creating registryctl   ... done
Creating harbor-db     ... done
Creating harbor-core   ... done
Creating clair         ... done
Creating clair-adapter ... done
Creating nginx             ... done
Creating harbor-jobservice ... done

status 를 확인한다.


root@newedu:~/harbor# docker-compose ps
      Name                     Command                  State                                   Ports
---------------------------------------------------------------------------------------------------------------------------------
chartmuseum         ./docker-entrypoint.sh           Up (healthy)
clair               ./docker-entrypoint.sh           Up (healthy)
clair-adapter       /home/clair-adapter/entryp ...   Up (healthy)
harbor-core         /harbor/entrypoint.sh            Up (healthy)
harbor-db           /docker-entrypoint.sh            Up (healthy)
harbor-jobservice   /harbor/entrypoint.sh            Up (healthy)
harbor-log          /bin/sh -c /usr/local/bin/ ...   Up (healthy)   127.0.0.1:1514->10514/tcp
harbor-portal       nginx -g daemon off;             Up (healthy)
nginx               nginx -g daemon off;             Up (healthy)   0.0.0.0:80->8080/tcp,:::80->8080/tcp,
                                                                    0.0.0.0:40002->8443/tcp,:::40002->8443/tcp
redis               redis-server /etc/redis.conf     Up (healthy)
registry            /home/harbor/entrypoint.sh       Up (healthy)
registryctl         /home/harbor/start.sh            Up (healthy)

2주차


CKA 문제 풀어보기


3주차


kubernetes에 Jenkins Master/Slave 구성하기


참고


로그인 한 후에 jenkins 폴더를 생성한다.


yaml 화일 참고 : https://github.com/shclub/jenkins


root@newedu:~# mkdir -p jenkins
root@newedu:~# cd jenkins

계정 생성과 RBAC 생성


jenkins admin 계정의 secret를 생성하기 위해 계정과 비밀번호를 base64로 인코딩 한다.


root@newedu:~/jenkins# echo -n 'admin' | base64
YWRtaW4=
root@newedu:~/jenkins# echo -n 'New1234!' | base64
TmV3MTIzNCE=

secret를 만들기 위해 yaml 화일을 생성한다.


root@newedu:~/jenkins# vi jenkins-edu-admin-secret.yaml

data 부분에 base64 인코딩 된 값을 넣어준다.


kind: Secret
apiVersion: v1
metadata:
  name: jenkins-admin-secret
data:
  jenkins-admin-user: YWRtaW4=
  jenkins-admin-password: TmV3MTIzNCE=
type: Opaque

secret를 생성하고 확인한다.


root@newedu:~/jenkins# kubectl apply -f jenkins-edu-admin-secret.yaml
secret/jenkins-admin-secret created
root@newedu:~/jenkins# kubectl get secret
NAME                                 TYPE                                  DATA   AGE
jenkins-admin-secret                 Opaque                                2      7s
my-service-account-dockercfg-4j5n7   kubernetes.io/dockercfg               1      169d
my-service-account-token-d67wk       kubernetes.io/service-account-token   4      169d
my-service-account-token-wxttf       kubernetes.io/service-account-token   4      169d
super-secret                         Opaque                                1      169d

jenkins-admin service account 를 생성 하고 role 과 rolebinding 을 생성한다.


root@newedu:~/jenkins# vi sa.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins-admin

root@newedu:~/jenkins# vi jenkins_rbac.yaml

jenkins-admin 권한은 최소화 한다.


apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: jenkins-admin
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
  resources: ["pods/exec"]
  verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get","list","watch"]
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get"]
- apiGroups: [""]
  resources: ["events"]
  verbs: ["get", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: jenkins-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: jenkins-admin
subjects:
- kind: ServiceAccount
  name: jenkins-admin

본인의 namespace 에 적용한다.


root@newedu:~/jenkins# kubectl apply -f jenkins_rbac.yaml
serviceaccount/jenkins-admin created
role.rbac.authorization.k8s.io/jenkins-admin created
rolebinding.rbac.authorization.k8s.io/jenkins-admin created

생성된 resource 들을 확인한다.


root@newedu:~/jenkins# kubectl get sa
NAME                 SECRETS   AGE
builder              2         247d
default              2         247d
deployer             2         247d
edu                  2         7d20h
jenkins-admin        2         10s
my-service-account   2         169d
root@newedu:~/jenkins# kubectl get role
NAME            CREATED AT
developer       2022-09-27T14:16:45Z
jenkins-admin   2023-03-16T02:03:11Z
pod-role        2022-09-27T14:04:40Z
root@newedu:~/jenkins# kubectl get rolebindings
NAME                              ROLE                                          AGE
admin                             ClusterRole/admin                             247d
developer-binding-myuser          Role/developer                                169d
edu30-admin                       ClusterRole/admin                             247d
jenkins-admin                     Role/jenkins-admin                            23s
pod-rolebinding                   Role/pod-role                                 169d
pod-rolebinding2                  Role/pod-role                                 7d19h
system:deployers                  ClusterRole/system:deployer                   247d
system:image-builders             ClusterRole/system:image-builder              247d
system:image-pullers              ClusterRole/system:image-puller               247d
system:openshift:scc:privileged   ClusterRole/system:openshift:scc:privileged   182d

jenkins-admin 으로 OKD에 접속하기 위해 anyuid, privileged 권한을 부여한다.

privileged 권한은 podman 에서 cri-o 런타임을 연결하기 위해 필요하다.


root@newedu:~/jenkins# oc adm policy add-scc-to-user anyuid -z jenkins-admin 
clusterrole.rbac.authorization.k8s.io/system:openshift:scc:anyuid added: "jenkins-admin"
root@newedu:~/jenkins# oc adm policy add-scc-to-user privileged -z jenkins-admin
clusterrole.rbac.authorization.k8s.io/system:openshift:scc:privileged added: "jenkins-admin"

Storage 설정


Jenkins 가 사용하는 stroage를 위해 pv / pvc 를 생성해야 하며 사전에 NFS 에 접속하여 폴더를 생성한다.


폴더는 사전에 강사가 생성하여 별도 생성 불필요


[root@edu jenkins]# mkdir -p edu
[root@edu jenkins]# mkdir -p edu1
[root@edu jenkins]# ls
edu  edu1  edu10  edu11  edu12  edu13  edu14  edu15  edu16  edu17  edu18  edu19  edu2  edu20  edu21  edu3  edu4  edu5  edu6  edu7  edu8  edu9

Jenkins slave 용 폴더도 생성한다.

[root@edu jenkins]# mkdir -p edu
[root@edu jenkins]# mkdir -p edu1_slave
...

jenkins master / slave 용 해당 폴더의 권한을 설정한다.


pod 내에서 nfs 연결해서 권한을 줄때는

chown -R nfsnobody:nfsnobody edu

대신 아래처럼 nobody:nogroup 으로 준다.

chown -R nobody:nogroup edu


[root@edu jenkins]# chown -R nfsnobody:nfsnobody edu
[root@edu jenkins]# chmod 777 edu
[root@edu jenkins]# chown -R nfsnobody:nfsnobody edu_slave
[root@edu jenkins]# chmod 777 edu_slave

여기부터는 생성해야 함.


Master용 PV 를 생성한다. 사이즈는 5G로 설정한다.


root@newedu:~/jenkins#  vi jenkins_pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins-edu-pv
spec:
  accessModes:
  - ReadWriteMany
  capacity:
    storage: 5Gi
  nfs:
    path: /share_8c0fade2_649f_4ca5_aeaa_8fd57904f8d5/jenkins/edu
    server: 172.25.1.162
  persistentVolumeReclaimPolicy: Retain

PV를 생성하고 Status를 확인해보면 Available 로 되어 있는 것을 알 수 있습니다.


root@newedu:~/jenkins# kubectl apply -f  jenkins_pv.yaml
persistentvolume/jenkins-edu-pv created
root@newedu:~/jenkins# kubectl get pv jenkins-edu-pv
NAME             CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
jenkins-edu-pv   5Gi        RWX            Retain           Available                                   10s

Master용 pvc 를 생성합니다. pvc 이름을 기억합니다.


root@newedu:~/jenkins# vi jenkins_pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-edu-pvc
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  volumeName: jenkins-edu-pv

Slave 용 PV 를 생성한다. 사이즈는 5G로 설정한다.


root@newedu:~/jenkins#  vi jenkins_slave_pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins-edu-slave-pv
spec:
  accessModes:
  - ReadWriteMany
  capacity:
    storage: 5Gi
  nfs:
    path: /share_8c0fade2_649f_4ca5_aeaa_8fd57904f8d5/jenkins/edu_slave
    server: 172.25.1.162
  persistentVolumeReclaimPolicy: Retain

PV를 생성하고 Status를 확인해보면 Available 로 되어 있는 것을 알 수 있습니다.


root@newedu:~/jenkins# kubectl apply -f  jenkins_slave_pv.yaml
persistentvolume/jenkins-edu-slave-pv created
root@newedu:~/jenkins# kubectl get pv jenkins-edu-slave-pv
NAME                   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                         STORAGECLASS   REASON   AGE
jenkins-edu-slave-pv   5Gi        RWX            Retain           Bound    edu30/jenkins-edu-slave-pvc                           100s

Slave 용 pvc 를 생성합니다. pvc 이름을 기억합니다.


root@newedu:~/jenkins# vi jenkins_slave_pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-edu-slave-pvc
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  volumeName: jenkins-edu-slave-pv

PVC를 생성할 때는 namespace ( 본인의 namespace ) 를 명시해야 합니다.
PVC 생성을 확인 해보고 다시 PV를 확인해 보면 Status가 Bound 로 되어 있는 것을 알 수 있습니다. 이제 PV 와 PVC가 연결이 되었습니다.


root@newedu:~/jenkins# kubectl apply -f jenkins_slave_pvc.yaml
persistentvolumeclaim/jenkins-edu-slave-pvc created
root@newedu:~/jenkins# kubectl get pvc
NAME                    STATUS   VOLUME                 CAPACITY   ACCESS MODES   STORAGECLASS   AGE
app-volume              Bound    app-config             1Gi        RWX            az-c           169d
jenkins-edu-pvc         Bound    jenkins-edu-pv         5Gi        RWX                           136m
jenkins-edu-slave-pvc   Bound    jenkins-edu-slave-pv   5Gi        RWX                           3m27s
root@newedu:~/jenkins# kubectl get pv jenkins-edu-slave-pv
NAME                   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                         STORAGECLASS   REASON   AGE
jenkins-edu-slave-pv   5Gi        RWX            Retain           Bound    edu30/jenkins-edu-slave-pvc                           4m

Helm Jenkins 설정


Jenkins 는 Helm Chart 를 이용하여 설치를 합니다.


현재 로컬의 helm repository 를 확인한다.


root@newedu:~/jenkins# helm repo list
NAME                           	URL
bitnami                        	https://charts.bitnami.com/bitnami
nfs-subdir-external-provisioner	https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/

jenkins helm repository를 아래와 같이 추가 한다.


root@newedu:~/jenkins# helm repo add jenkins https://charts.jenkins.io --insecure-skip-tls-verify
"jenkins" has been added to your repositories

helm repository를 update 한다.


root@newedu:~/jenkins# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "nfs-subdir-external-provisioner" chart repository
...Successfully got an update from the "jenkins" chart repository
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!

jenkins helm reppository 에서 helm chart를 검색을 하고 jenkins chart를 선택합니다.


root@newedu:~/jenkins# helm search repo jenkins
NAME           	CHART VERSION	APP VERSION	DESCRIPTION
bitnami/jenkins	12.0.1       	2.387.1    	Jenkins is an open source Continuous Integratio...
jenkins/jenkins	4.3.8        	2.387.1    	Jenkins - Build great things at any scale! The ...

jenkins/jenkins 차트에서 차트의 변수 값을 변경하기 위해 jenkins_values.yaml 화일을 추출한다.


root@newedu:~/jenkins#  helm show values jenkins/jenkins > jenkins_values.yaml

vi 데이터에서 생성된 jenkins_values.yaml을 연다.


root@newedu:~# vi jenkins_values.yaml

라인을 보기 위해 ESC 를 누른 후 :set nu 를 입력하면 왼쪽에 라인이 보인다.

아래 라인을 찾아 값을 변경한다. 51번 라인에 앞에서 생성한 secret을 넣는다.


 50   admin:
 51     existingSecret: "jenkins-admin-secret" #
 52     userKey: jenkins-admin-user
 53     passwordKey: jenkins-admin-password

kubernetes plugin은 반드시 아래와 같이 수정 필요.
현재 jenkins과 호환되는 버전


 91   javaOpts: "-Duser.timezone=Asia/Seoul"
 ...
244   installPlugins:
245     - kubernetes:3842.v7ff395ed0cf3 #3734.v562b_b_a_627ea_c
246     - workflow-aggregator:590.v6a_d052e5a_a_b_5
247     - git:4.13.0
248     - configuration-as-code:1569.vb_72405b_80249
...
508   # Openshift route
509   route:
510     enabled: true  # true 로 변경
511     labels: {}
512     annotations: {}
...
617 agent:
618   enabled: true
619   defaultsProviderTemplate: ""
620   # URL for connecting to the Jenkins contoller
621   jenkinsUrl:
622   # connect to the specified host and port, instead of connecting directly to the Jenkins controller
623   jenkinsTunnel:
624   kubernetesConnectTimeout: 5
625   kubernetesReadTimeout: 15
626   maxRequestsPerHostStr: "32"
627   namespace:
628   image: "jenkins/jnlp-slave" #"jenkins/inbound-agent"
629   tag: "latest-jdk11"
...
662   volumes: # []
663   # - type: ConfigMap
664   #   configMapName: myconfigmap
665   #   mountPath: /var/myapp/myconfigmap
666   # - type: EmptyDir
667   #   mountPath: /var/myapp/myemptydir
668   #   memory: false
669   # - type: HostPath
670   #   hostPath: /var/lib/containers
671   #   mountPath: /var/myapp/myhostpath
672   # - type: Nfs
673   #   mountPath: /var/myapp/mynfs
674   #   readOnly: false
675   #   serverAddress: "192.0.2.0"
676   #   serverPath: /var/lib/containers
677    - type: PVC
678      claimName: jenkins-edu-slave-pvc
679      mountPath: /var/jenkins_home
680      readOnly: false
...
691   workspaceVolume: # {}
692   ## DynamicPVC example
693   # type: DynamicPVC
694   # configMapName: myconfigmap
695   ## EmptyDir example
696   # type: EmptyDir
697   # memory: false
698   ## HostPath example
699   # type: HostPath
700   # hostPath: /var/lib/containers
701   ## NFS example
702   # type: Nfs
703   # readOnly: false
704   # serverAddress: "192.0.2.0"
705   # serverPath: /var/lib/containers
706   ## PVC example
707    type: PVC
708    claimName: jenkins-edu-slave-pvc
709    readOnly: false
710   #
710   #
...

822 persistence:
823   enabled: true
824   ## A manually managed Persistent Volume and Claim
825   ## Requires persistence.enabled: true
826   ## If defined, PVC must be created manually before volume will be bound
827   existingClaim: "jenkins-edu-pvc" #
828   ## jenkins data Persistent Volume Storage Class
829   ## If defined, storageClassName: <storageClass>
830   ## If set to "-", storageClassName: "", which disables dynamic provisioning
831   ## If undefined (the default) or set to null, no storageClassName spec is
832   ##   set, choosing the default provisioner.  (gp2 on AWS, standard on
833   ##   GKE, AWS & OpenStack)
834   ##
835   storageClass:
836   annotations: {}
837   labels: {}
838   accessMode: "ReadWriteOnce"
839   size: "5Gi"  # "8Gi"  
...

870 serviceAccount:
871   create: false #  이미 생성 했기 때문에 false 로 변경
872   # The name of the service account is autogenerated by default
873   name: "jenkins-admin" #
874   annotations: {}
875   extraLabels: {}
876   imagePullSecretName:

Helm 으로 Jenkins 설치


jenkins_values.yaml 를 사용하여 설치 한다.


root@newedu:~/jenkins# helm install jenkins  -f jenkins_values.yaml jenkins/jenkins
NAME: jenkins
LAST DEPLOYED: Thu Mar 16 16:33:52 2023
NAMESPACE: edu30
STATUS: deployed
REVISION: 1
NOTES:
1. Get your 'admin' user password by running:
  kubectl exec --namespace edu30 -it svc/jenkins -c jenkins -- /bin/cat /run/secrets/additional/chart-admin-password && echo
2. Get the Jenkins URL to visit by running these commands in the same shell:
  echo http://127.0.0.1:8080
  kubectl --namespace edu30 port-forward svc/jenkins 8080:8080

3. Login with the password from step 1 and the username: admin
4. Configure security realm and authorization strategy
5. Use Jenkins Configuration as Code by specifying configScripts in your values.yaml file, see documentation: http://127.0.0.1:8080/configuration-as-code and examples: https://github.com/jenkinsci/configuration-as-code-plugin/tree/master/demos

For more information on running Jenkins on Kubernetes, visit:
https://cloud.google.com/solutions/jenkins-on-container-engine

For more information about Jenkins Configuration as Code, visit:
https://jenkins.io/projects/jcasc/


NOTE: Consider using a custom image with pre-installed plugins

설치가 완료되면 pod를 조회하여 jenkins-0 pod가 있는지 확인한다.
처음에는 plugin 이 설치가 됨으로 빠를 때는 1분 이내 늦을때는 8~10분 정도 시간이 필요하다.


root@newedu:~/jenkins# kubectl get po
NAME                                        READY   STATUS    RESTARTS   AGE
jenkins-0                                   2/2     Running   0          18h
nfs-test-589c488d6f-8lk5p                   1/1     Running   0          40h

아래 명령어로 plugin 설치가 되는 과정을 볼 수 있다.


root@newedu:~/jenkins# kubectl logs jenkins1-0 -c init 

Jenkins 설정


웹 브라우저 에서 본인의 jenkins 로 접속한다.
route 정보를 모르면 아래 명령어로 조회한다.


root@newedu:~/jenkins# kubectl get route
NAME      HOST/PORT                                 PATH   SERVICES   PORT   TERMINATION     WILDCARD
jenkins   jenkins-edu30.apps.211-34-231-82.nip.io          jenkins    http   edge/Redirect   None

https://jenkins-edu30.apps.211-34-231-82.nip.io/


아래 처럼 로그인 화면이 나오면 admin 계정으로 로그인한다.



Manage Jenkins 메뉴를 클릭한다.


Manage Plugins 메뉴를 클릭한다.



Plugin은 3 가지를 설치한다.

  • git parameter
  • pipeline stage view
  • docker pipeline

git parameter


install without restart 로 설치한다.


pipeline stage view

docker pipeline


plugin 설정을 완료 하면 Manage jenkins -> Manage Nodes and Clouds 메뉴로 이동하여 Jenkins 설정을 한다.



Configure Clouds로 이동한다.



Configure Clouds에 가면 kubernetes Cloud Details 가 Jenkins Master 설정이고 Pod Template 에 Slave 설정을 하면 된다.



kubernetes Cloud Details 를 확장하여 자세히 보면 Jenkins Master POD 가 있는 namespace 를 확인 할 수 있다.


기본값으로 설정된 값을 유지한다.



POD Labels의 key 값만 변경한다. 안해도 상관 없음




Pod Template 을 확장하여 Slave 설정을 시작한다.
우리는 OKD 에 Jenkins를 설치를 하였고 OKD는 docker runtime 대신 CRI-O runtime을 사용하기 때문에 podman 으로 docker 를 대신한다.


podman-agent라는 이름으로 설정을 하고 Pod template details 를 클릭한다.



Pod Template Label은 podman-agent 로 설정한다.


하나의 POD에 2개의 컨테이너를 설정한다.

  • JNLP 컨테이너 : jenkins slave 를 위한 컨테이너
  • Podman 컨테이너 : Podman 프로그램이 설치된 컨테이너
    • podman 컨테이너의 /etc/containers와 /var/lib/containers를 hostpath로 연결해 주어야 한다.
    • podman에서 worker node의 cri-o 런타임 엔진을 사용

먼저 jnlp 컨테이너 값을 설정한다.

  • docker image 를 변경한다 : jenkins/jnlp-slave:latest-jdk11
  • command to run 을 기존 sleep 값을 지운다.


해당 slave pod에 추가 컨테이너 값을 설정하기 위해 add container를 클릭하고 Container Template를 선택한다.



podman 컨테이너를 설정한다.

  • 이름을 설정한다 : podman
  • docker image 를 설정한다 : mattermost/podman:1.8.0
  • Advanced 메뉴를 클릭하고 Run in privileged mode를 체크한다. (hostpath)


Add volume 버튼을 클릭하고 slave 용 pvc와 podman용 hostpath를 설정한다.

slave용 pvc는 이미 설정이되어 있기 때문에 hostpath 만 설정해도 된다.



아래로 이동하여 service account를 jenkins-admin 으로 변경한다.

workspace volume 도 helm 에서 사전에 설정 했기 때문에 확인만 하고 save 버튼을 눌러 저장한다.



Jenkins Pipeline 생성


대쉬보드로 이동하여 신규 pipeline을 구성 한다.

New Item 버튼을 클릭한다.


Item 이름을 설정하고 pipeline 을 선택한 후 ok 버튼을 클릭한다.



Git Parameter 를 설정 한다. Git Parameter 가 안보이는 수강생은 plugins 에서 git parameter를 설치해야 한다.



git Repostory는 본인의 git 주소를 입력하고 script path를 입력한다.


강사 edu1 repository ( (https://github.com/shclub/edu1) )의 Jenkinsfile_okd 화일을 참고하여 신규 생성한다.
( 현재 OKD 네트웍 이슈로 Harbor로 연결 안되어 docker hub 로 연결 하여 생성. Jenkinsfile_dockerhub 참고 )



credential은 Add 버튼을 클릭하여 github_ci 와 harbor_ci 라는 이름 으로 생성한다.

harbor 가 구성이 안된 사람은 id는 edu , 비밀번호는 New1234! 로 구성하여 생성한다.



github의 Jenkinsfile_okd 는 본인의 환경에 맞게 변경한다.

  • def docker_registry = "https://211.252.85.148:40002"
  • def imageName = "211.252.85.148:40002/edu/edu1"
    • 예) edu1 : 211.43.12.162:40002/<본인프로젝트>/edu1
    • 예) 본인 Harbor 가 없으면서 순번이 2번인 경우 : 211.252.85.148:40002/edu2/edu1
  • podTemplate의 namespace는 본인의 namespace 로 반드시 변경
  • persistentVolumeClaim 의 claim은 본인의 slave claim으로 변경
    • 예) edu1 : jenkins-edu1-slave-pvc
  • git url은 본인의 github repository 로 변경
  • harbor credential은 다른 이름으로 생성했으면 그것에 맞추어 변경


대쉬보드로 돌아와서 본인의 파이프 라인 클릭



Build with parameters 로 실행



성공적으로 진행이 된 것을 확인한다.



Harbor로 이동하여 이미지가 정상적으로 Push 되었는지 확인한다.

강사의 Harbor : https://211.252.85.148:40002/

  • edu 계정으로 로그인
  • 본인의 project로 이동하여 edu1 도커이미지 확인


강사의 Jenkjns : https://jenkins-edu30.apps.211-34-231-82.nip.io/

  • 계정 : admin

4주차


kubernetes에 CI/CD 구성 (CI : Maven/Skaffold/SonarQube , CD : ArgoCD/kustomize )


참고


VM에 로그인 한 후에 sonar 폴더를 생성한다.


yaml 화일 참고 : https://github.com/shclub/sonarqube


root@newedu:~# mkdir -p sonar
root@newedu:~# cd sonar

Storage 설정


PostgreSQL 과 SonarQube 가 사용하는 stroage를 위해 pv / pvc 를 생성해야 하며 사전에 NFS 에 접속하여 폴더를 생성한다.


postgresql 은 아래 폴더에 생성되어 있고 수강생은 본인의 폴더 직접 생성.


[root@edu postgre]# pwd
/mnt/postgre
[root@edu postgre]# mkdir -p edu

SonarQube 용 폴더도 생성한다.

[root@edu sonar]# pwd
/mnt/sonar
[root@edu sonar]# mkdir -p edu
...

postgresql / SonarQube 용 해당 폴더의 권한을 설정한다.


pod 내에서 nfs 연결해서 권한을 줄때는

chown -R nfsnobody:nfsnobody edu

대신 아래처럼 nobody:nogroup 으로 준다.

chown -R nobody:nogroup edu


[root@edu postgre]# chown -R nfsnobody:nfsnobody edu
[root@edu postgre]# chmod 777 edu
[root@edu sonar]# chown -R nfsnobody:nfsnobody edu
[root@edu sonar]# chmod 777 edu

postgresql 용 PV 를 생성한다. 사이즈는 5G로 설정한다.


root@newedu:~/sonar#  vi postgre_pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgre-edu-pv
spec:
  accessModes:
  - ReadWriteMany
  capacity:
    storage: 5Gi
  nfs:
    path: /share_8c0fade2_649f_4ca5_aeaa_8fd57904f8d5/postgre/edu
    server: 172.25.1.162
  persistentVolumeReclaimPolicy: Retain

PV를 생성하고 Status를 확인해보면 Available 로 되어 있는 것을 알 수 있습니다.


root@newedu:~/sonar# kubectl apply -f  postgre_pv.yaml

postgresql용 pvc 를 생성합니다. pvc 이름을 기억합니다.


root@newedu:~/sonar# vi postgre_pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgre-edu-pvc
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  volumeName: postgre-edu-pv

SonarQube 용 PV 를 생성한다. 사이즈는 5G로 설정한다.


root@newedu:~/sonar#  vi sonar_pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: sonar-edu-pv
spec:
  accessModes:
  - ReadWriteMany
  capacity:
    storage: 5Gi
  nfs:
    path: /share_8c0fade2_649f_4ca5_aeaa_8fd57904f8d5/sonar/edu
    server: 172.25.1.162
  persistentVolumeReclaimPolicy: Retain

PV를 생성하고 Status를 확인해보면 Available 로 되어 있는 것을 알 수 있습니다.


root@newedu:~/sonar# kubectl apply -f  sonar_pvc.yaml

SonarQube 용 pvc 를 생성합니다. pvc 이름을 기억합니다.


root@newedu:~/sonar# vi sonar_pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: sonar-edu-pvc
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  volumeName: sonar-edu-pv

PVC를 생성할 때는 namespace ( 본인의 namespace ) 를 명시해야 합니다.
PVC 생성을 확인 해보고 다시 PV를 확인해 보면 Status가 Bound 로 되어 있는 것을 알 수 있습니다. 이제 PV 와 PVC가 연결이 되었습니다.


root@newedu:~/sonar# kubectl apply -f sonar_pvc.yaml

Helm 으로 PostgreSQL 설치


helm repo 업데이트를 합니다.

root@newedu:~/sonar# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "jenkins" chart repository
...Successfully got an update from the "nfs-subdir-external-provisioner" chart repository
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!

helm 에서 postgreSQL 를 검색합니다.

root@newedu:~/sonar# helm search repo postgresql
NAME                 	CHART VERSION	APP VERSION	DESCRIPTION
bitnami/postgresql   	12.2.6       	15.2.0     	PostgreSQL (Postgres) is an open source object-...
bitnami/postgresql-ha	11.2.0       	15.2.0     	This PostgreSQL cluster solution includes the P...
bitnami/supabase     	0.1.4        	0.23.2     	Supabase is an open source Firebase alternative...

bitnami/postgresql 차트에서 차트의 변수 값을 변경하기 위해 postgre_values.yaml 화일을 추출한다.


root@newedu:~/sonar#  helm show values bitnami/postgresql  > postgre_values.yaml

postgre_values.yaml 를 수정한다.

  • 28 ,29,30,31 : 본인이 DB 계정 설정
  • 646 : 본인의 pvc로 변경
  • 669 : 5G로 사이즈 변경
  • 694 : read replica 0 ( primary db만 사용 , readReplicas 는 사용 안함)

  27     auth:
  28       postgresPassword: "edu1234"
  29       username: "edu"
  30       password: "edu1234"
  31       database: "edu"
  32       existingSecret: ""
 ... 
 640   persistence:
 641     ## @param primary.persistence.enabled Enable PostgreSQL Primary data persistence using PVC
 642     ##
 643     enabled: true
 644     ## @param primary.persistence.existingClaim Name of an existing PVC to use
 645     ##
 646     existingClaim: "postgre-edu-pvc"
 647     ## @param primary.persistence.mountPath The path the volume will be mounted at
 648     ## Note: useful when using custom PostgreSQL images
 649     ##
 650     mountPath: /bitnami/postgresql
 651     ## @param primary.persistence.subPath The subdirectory of the volume to mount to
 652     ## Useful in dev environments and one PV for multiple services
 653     ##
 654     subPath: ""
 655     ## @param primary.persistence.storageClass PVC Storage Class for PostgreSQL Primary data volume
 656     ## If defined, storageClassName: <storageClass>
 657     ## If set to "-", storageClassName: "", which disables dynamic provisioning
 658     ## If undefined (the default) or set to null, no storageClassName spec is
 659     ##   set, choosing the default provisioner.  (gp2 on AWS, standard on
 660     ##   GKE, AWS & OpenStack)
 661     ##
 662     storageClass: ""
 663     ## @param primary.persistence.accessModes PVC Access Mode for PostgreSQL volume
 664     ##
 665     accessModes:
 666       - ReadWriteOnce
 667     ## @param primary.persistence.size PVC Storage Request for PostgreSQL volume
 668     ##
 669     size: 5Gi
...
 688 readReplicas:
 689   ## @param readReplicas.name Name of the read replicas database (eg secondary, slave, ...)
 690   ##
 691   name: read
 692   ## @param readReplicas.replicaCount Number of PostgreSQL read only replicas
 693   ##
 694   replicaCount: 0

이제 postgreSQL DB를 설치 합니다.


root@newedu:~/sonar# helm install sonar-postgre bitnami/postgresql -f postgre_values.yaml
NAME: sonar-postgre
LAST DEPLOYED: Mon Mar 27 10:12:46 2023
NAMESPACE: edu30
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: postgresql
CHART VERSION: 12.2.6
APP VERSION: 15.2.0

** Please be patient while the chart is being deployed **

PostgreSQL can be accessed via port 5432 on the following DNS names from within your cluster:

    sonar-postgre-postgresql.edu30.svc.cluster.local - Read/Write connection

To get the password for "postgres" run:

    export POSTGRES_ADMIN_PASSWORD=$(kubectl get secret --namespace edu30 sonar-postgre-postgresql -o jsonpath="{.data.postgres-password}" | base64 -d)

To get the password for "edu" run:

    export POSTGRES_PASSWORD=$(kubectl get secret --namespace edu30 sonar-postgre-postgresql -o jsonpath="{.data.password}" | base64 -d)

To connect to your database run the following command:

    kubectl run sonar-postgre-postgresql-client --rm --tty -i --restart='Never' --namespace edu30 --image docker.io/bitnami/postgresql:15.2.0-debian-11-r14 --env="PGPASSWORD=$POSTGRES_PASSWORD" \
      --command -- psql --host sonar-postgre-postgresql -U edu -d edu -p 5432

    > NOTE: If you access the container using bash, make sure that you execute "/opt/bitnami/scripts/postgresql/entrypoint.sh /bin/bash" in order to avoid the error "psql: local user with ID 1001} does not exist"

To connect to your database from outside the cluster execute the following commands:

    kubectl port-forward --namespace edu30 svc/sonar-postgre-postgresql 5432:5432 &
    PGPASSWORD="$POSTGRES_PASSWORD" psql --host 127.0.0.1 -U edu -d edu -p 5432

WARNING: The configured password will be ignored on new installation in case when previous Posgresql release was deleted through the helm command. In that case, old PVC will have an old password, and setting it through helm won't take effect. Deleting persistent volumes (PVs) will solve the issue.

pod를 확인한다.

root@newedu:~/sonar# kubectl get po
NAME                                        READY   STATUS    RESTARTS   AGE
sonar-postgre-postgresql-0                  1/1     Running   0          82s

NFS 서버에 접속하여 data 폴더가 생성 되었는지 확인한다.

[root@edu edu]# pwd
/mnt/postgre/edu
[root@edu edu]# ls data
PG_VERSION  pg_commit_ts   pg_logical    pg_replslot   pg_stat      pg_tblspc    pg_xact               postmaster.pid
base        pg_dynshmem    pg_multixact  pg_serial     pg_stat_tmp  pg_twophase  postgresql.auto.conf
global      pg_ident.conf  pg_notify     pg_snapshots  pg_subtrans  pg_wal       postmaster.opts

Helm 으로 SonarQube 설치


helm repo 업데이트를 합니다.

root@newedu:~/sonar# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "jenkins" chart repository
...Successfully got an update from the "nfs-subdir-external-provisioner" chart repository
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!

helm 에서 sonarqube 를 검색합니다.

root@newedu:~/sonar# helm search repo sonarqube
NAME                 	CHART VERSION	APP VERSION	DESCRIPTION
NAME             	CHART VERSION	APP VERSION    	DESCRIPTION
bitnami/sonarqube	2.1.4        	9.9.0          	SonarQube(TM) is an open source quality managem...

bitnami/sonarqube 차트에서 차트의 변수 값을 변경하기 위해 sonarqube_values.yaml 화일을 추출한다.


root@newedu:~/sonar#  helm show values bitnami/sonarqube  > sonarqube_values.yaml

먼저 postgreSQL DB의 서비스 이름을 확인합니다.

root@newedu:~/sonar# kubectl get svc
NAME                          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
jenkins                       NodePort    172.30.247.178   <none>        8080:30332/TCP   10d
jenkins-agent                 ClusterIP   172.30.140.40    <none>        50000/TCP        10d
sonar-postgre-postgresql      ClusterIP   172.30.225.163   <none>        5432/TCP         19m

sonarqube_values.yaml 를 수정한다.

  • 708 : pvc 사용으로 true
  • 722 : 5G로 사이즈 변경
  • 728 : 본인의 pvc로 변경
  • 1004 : 이미 postgresql db 설치 했기 때문에 false
  • 1065 : postgresql db 서비스 이름 ( 위에서 조회 )

 706 persistence:
 707   ## @param persistence.enabled Enable persistence using Persistent Volume Claims
 708   ##
 709   enabled: true
 710   ## @param persistence.storageClass Persistent Volume storage class
 711   ## If defined, storageClassName: <storageClass>
 712   ## If set to "-", storageClassName: "", which disables dynamic provisioning
 713   ## If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner
 714   ##
 715   storageClass: ""
 716   ## @param persistence.accessModes [array] Persistent Volume access modes
 717   ##
 718   accessModes:
 719     - ReadWriteOnce
 720   ## @param persistence.size Persistent Volume size
 721   ##
 722   size: 5Gi
 723   ## @param persistence.dataSource Custom PVC data source
 724   ##
 725   dataSource: {}
 726   ## @param persistence.existingClaim The name of an existing PVC to use for persistence
 727   ##
 728   existingClaim: "sonar-edu-pvc"
...
1001 postgresql:
1002   ## @param postgresql.enabled Deploy PostgreSQL subchart
1003   ##
1004   enabled: false
1005   ## @param postgresql.nameOverride Override name of the PostgreSQL chart
...
1062 externalDatabase:
1063   ## @param externalDatabase.host Host of an external PostgreSQL instance to connect (only if postgresql.enabled=false)
1064   ##
1065   host: "sonar-postgre-postgresql"
1066   ## @param externalDatabase.user User of an external PostgreSQL instance to connect (only if postgresql.enabled=false)
1067   ##
1068   user: edu
1069   ## @param externalDatabase.password Password of an external PostgreSQL instance to connect (only if postgresql.enabled=fa     lse)
1070   ##
1071   password: "edu1234"
1072   ## @param externalDatabase.existingSecret Secret containing the password of an external PostgreSQL instance to connect (o     nly if postgresql.enabled=false)
1073   ## Name of an existing secret resource containing the DB password in a 'password' key
1074   ##
1075   existingSecret: ""
1076   ## @param externalDatabase.database Database inside an external PostgreSQL to connect (only if postgresql.enabled=false)
1077   ##
1078   database: edu

sonarqube를 설치 하기 전에 sonarqube service account 에게 권한을 부여합니다.

root@newedu:~/sonar# oc adm policy add-scc-to-user anyuid -z sonarqube
root@newedu:~/sonar# oc adm policy add-scc-to-user privileged -z sonarqube

이제 sonarqube 를 설치 합니다.

root@newedu:~/sonar# helm install sonarqube bitnami/sonarqube -f sonarqube_values.yaml
NAME: sonarqube
LAST DEPLOYED: Mon Mar 27 10:51:23 2023
NAMESPACE: edu30
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
** Please be patient while the chart is being deployed **

Your SonarQube(TM) site can be accessed through the following DNS name from within your cluster:

    sonarqube.edu30.svc.cluster.local (port 80)

To access your SonarQube(TM) site from outside the cluster follow the steps below:

1. Get the SonarQube(TM) URL by running these commands:

  NOTE: It may take a few minutes for the LoadBalancer IP to be available.
        Watch the status with: 'kubectl get svc --namespace edu30 -w sonarqube'

   export SERVICE_IP=$(kubectl get svc --namespace edu30 sonarqube --template "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}")
   echo "SonarQube(TM) URL: http://$SERVICE_IP/"

2. Open a browser and access SonarQube(TM) using the obtained URL.

3. Login with the following credentials below:

  echo Username: user
  echo Password: $(kubectl get secret --namespace edu30 sonarqube -o jsonpath="{.data.sonarqube-password}" | base64 -d)

pod를 확인한다.

pod가 기동 되지 않으면 kubectl get events 명령어로 조회를 한다.

privileged 관련 오류가 발생하면 아래와 같이 권한을 부여하고

helm delete sonarqube 한 후 다시 설치한다.


중요 : 재설치를 하는 경우 NFS 폴더의 데이터를 다 삭제하고 설치해야 함.


root@newedu:~/sonar# oc adm policy add-scc-to-user privileged -z sonarqube

root@newedu:~/sonar# kubectl get po
NAME                                        READY   STATUS    RESTARTS   AGE
jenkins-0                                   2/2     Running   26         9d
nfs-test-589c488d6f-8lk5p                   1/1     Running   2          10d
sonar-postgre-postgresql-0                  1/1     Running   0          54m
sonarqube-5d48b66455-5sgzh                  1/1     Running   0          3m7s

NFS 서버에 접속하여 data 폴더가 생성 되었는지 확인한다.

[root@edu edu]# pwd
/mnt/sonar/edu
[root@edu edu]# ls
data  extensions

서비스를 조회해서 LoadBalancer Type을 NodePort로 변경합니다.

root@newedu:~/sonar# kubectl get svc
NAME                          TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                       AGE
jenkins                       NodePort       172.30.247.178   <none>        8080:30332/TCP                10d
jenkins-agent                 ClusterIP      172.30.140.40    <none>        50000/TCP                     10d
sonar-postgre-postgresql      ClusterIP      172.30.225.163   <none>        5432/TCP                      64m
sonar-postgre-postgresql-hl   ClusterIP      None             <none>        5432/TCP                      64m
sonarqube                     LoadBalancer   172.30.154.233   <pending>     80:30262/TCP,9001:30118/TCP   13m

root@newedu:~/sonar# kubectl edit svc sonarqube
service/sonarqube edited

root@newedu:~/sonar# kubectl get svc
NAME                          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                       AGE
jenkins                       NodePort    172.30.247.178   <none>        8080:30332/TCP                10d
jenkins-agent                 ClusterIP   172.30.140.40    <none>        50000/TCP                     10d
sonar-postgre-postgresql      ClusterIP   172.30.225.163   <none>        5432/TCP                      64m
sonar-postgre-postgresql-hl   ClusterIP   None             <none>        5432/TCP                      64m
sonarqube                     NodePort    172.30.154.233   <none>        80:30262/TCP,9001:30118/TCP   13m

웹브라우저에서 http://211.34.231.84:30262/ 로 접속하여 로그인합니다.

  • id 는 user
  • 비밀번호는 아래와 같이 추출합니다.
    root@newedu:~/sonar# kubectl get secret  sonarqube -o jsonpath="{.data.sonarqube-password}" | base64 -d
    agLMkOCVqv


SonarQube 로 프로젝트 구성


참고


SonarQube에 로그인 한 후에 제일 먼저 Admin 비밀빌번호를 변경한다.
Account -> My Account 클릭한 후 security tab으로 이동한다.


비밀번호를 변경한다.


프로젝트를 생성한다. manual 로 만든다.


원하는 이름을 넣고 branch에는 master를 입력한다.


그 다음 with Jenkins를 클릭하여 필요한 정보를 확인한다. 설정은 별도로 안해도 된다.



Jenkins와 연동하기 위해서는 Token 값을 생성해야 합니다.
계정으로 이동합니다.


security 탭으로 이동합니다.


Type은 Project Analysys Token 으로 설정하고 Generate 버튼을 클릭합니다.


생성된 Token 값을 COPY 버튼을 클릭하여 복사하여 적당하 곳에 저장합니다.


SonarQube 프로젝트를 구성하기 위해서는 backend 인 SpringBoot의 pom.xml 화일에 dependency를 추가 한다.

		<plugin>
				<groupId>org.sonarsource.scanner.maven</groupId>
				<artifactId>sonar-maven-plugin</artifactId>
				<version>3.7.0.1746</version>
		</plugin>

이제 jenkins를 설정합니다.

Jenkins는 먼저 SonarQube plugins 을 설치합니다.

  • Sonarqube Scanner
  • Sonar Quality Gates


Manage Jenkins -> Configure System 으로 이동하여 SonarQube 서버 설정을 합니다.


Add Credential 을 추가 하는데 Secret Text로 설정하여 SonarQube Token 값을 설정 합니다.


설정한 정보가 맞는지 확인 하고 save 합니다.


Manage Jenkins -> Global Tool Configuration 이동하여 Sonar Scanner 설정을 합니다.


이제 설정이 완료 되었음으로 Dashboard -> New Item 으로 이동하여 새로운 Pipeline을 생성합니다. edu-backend pipeline을 복사하여 생성하며 jenkins 화일 이름만 변경 합니다.


Build with paramameter를 클릭하여 실행을 하고 console Output 을 확인합니다.

아래와 같은 메시지가 나오면 연동이 성공 한 것입니다.

Downloaded from central: https://repo.maven.apache.org/maven2/org/sonarsource/scanner/api/sonar-scanner-api/2.14.0.2002/sonar-scanner-api-2.14.0.2002.jar (625 kB at 42 MB/s)
Downloaded from central: https://repo.maven.apache.org/maven2/org/sonatype/plexus/plexus-sec-dispatcher/1.4/plexus-sec-dispatcher-1.4.jar (28 kB at 792 kB/s)
[INFO] User cache: /root/.sonar/cache
[INFO] SonarQube version: 9.9.0.65466
[INFO] Default locale: "en_US", source code encoding: "UTF-8"
[INFO] Load global settings
[INFO] Load global settings (done) | time=126ms
[INFO] Server id: 296AEF55-AYcgz2EaLi3MhYbotBq5
[INFO] User cache: /root/.sonar/cache
[INFO] Load/download plugins
[INFO] Load plugins index
[INFO] Load plugins index (done) | time=67ms
[INFO] Load/download plugins (done) | time=1142ms
[INFO] Process project properties
[INFO] Process project properties (done) | time=10ms
[INFO] Execute project builders
[INFO] Execute project builders (done) | time=2ms
[INFO] Project key: edu
[INFO] Base dir: /home/jenkins/agent/workspace/edu_backend_sonar
[INFO] Working dir: /home/jenkins/agent/workspace/edu_backend_sonar/target/sonar
[INFO] Load project settings for component key: 'edu'
[INFO] Load project settings for component key: 'edu' (done) | time=26ms
[INFO] Auto-configuring with CI 'Jenkins'
[INFO] Load quality profiles
[INFO] Load quality profiles (done) | time=69ms
[INFO] Load active rules
[INFO] Load active rules (done) | time=1556ms
[INFO] Load analysis cache
[INFO] Load analysis cache (404) | time=9ms
[INFO] Load project repositories
[INFO] Load project repositories (done) | time=22ms
[INFO] Indexing files...
[INFO] Project configuration:
[INFO] 25 files indexed
[INFO] 0 files ignored because of scm ignore settings
[INFO] Quality profile for java: Sonar way
[INFO] Quality profile for xml: Sonar way
[INFO] ------------- Run sensors on module thirdproject
[INFO] Load metrics repository
[INFO] Load metrics repository (done) | time=20ms
[INFO] Sensor JavaSensor [java]
[INFO] Configured Java source version (sonar.java.source): 17
[INFO] JavaClasspath initialization
[INFO] JavaClasspath initialization (done) | time=6ms
[INFO] JavaTestClasspath initialization
[INFO] JavaTestClasspath initialization (done) | time=3ms
[INFO] Server-side caching is enabled. The Java analyzer will not try to leverage data from a previous analysis.
[INFO] Using ECJ batch to parse 22 Main java source files with batch size 189 KB.
[INFO] Starting batch processing.
[INFO] The Java analyzer cannot skip unchanged files in this context. A full analysis is performed for all files.
[INFO] 100% analyzed
[INFO] Batch processing: Done.
[INFO] Did not optimize analysis for any files, performed a full analysis for all 22 files.
[WARNING] Use of preview features have been detected during analysis. Enable DEBUG mode to see them.
[INFO] Using ECJ batch to parse 2 Test java source files with batch size 189 KB.
[INFO] Starting batch processing.
[INFO] 100% analyzed
[INFO] Batch processing: Done.
[INFO] Did not optimize analysis for any files, performed a full analysis for all 2 files.
[INFO] No "Generated" source files to scan.
[INFO] Sensor JavaSensor [java] (done) | time=2181ms
[INFO] Sensor JaCoCo XML Report Importer [jacoco]
[INFO] 'sonar.coverage.jacoco.xmlReportPaths' is not defined. Using default locations: target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml,build/reports/jacoco/test/jacocoTestReport.xml
[INFO] No report imported, no coverage information will be imported by JaCoCo XML Report Importer
[INFO] Sensor JaCoCo XML Report Importer [jacoco] (done) | time=2ms
[INFO] Sensor CSS Rules [javascript]
[INFO] No CSS, PHP, HTML or VueJS files are found in the project. CSS analysis is skipped.
[INFO] Sensor CSS Rules [javascript] (done) | time=0ms
[INFO] Sensor C# Project Type Information [csharp]
[INFO] Sensor C# Project Type Information [csharp] (done) | time=1ms
[INFO] Sensor C# Analysis Log [csharp]
[INFO] Sensor C# Analysis Log [csharp] (done) | time=11ms
[INFO] Sensor C# Properties [csharp]
[INFO] Sensor C# Properties [csharp] (done) | time=0ms
[INFO] Sensor SurefireSensor [java]
[INFO] parsing [/home/jenkins/agent/workspace/edu_backend_sonar/target/surefire-reports]
[INFO] Sensor SurefireSensor [java] (done) | time=55ms
[INFO] Sensor HTML [web]
[INFO] Sensor HTML [web] (done) | time=2ms
[INFO] Sensor XML Sensor [xml]
[INFO] 1 source file to be analyzed
[INFO] 1/1 source file has been analyzed
[INFO] Sensor XML Sensor [xml] (done) | time=217ms
[INFO] Sensor TextAndSecretsSensor [text]
[INFO] 25 source files to be analyzed
[INFO] 25/25 source files have been analyzed
[INFO] Sensor TextAndSecretsSensor [text] (done) | time=42ms
[INFO] Sensor VB.NET Project Type Information [vbnet]
[INFO] Sensor VB.NET Project Type Information [vbnet] (done) | time=1ms
[INFO] Sensor VB.NET Analysis Log [vbnet]
[INFO] Sensor VB.NET Analysis Log [vbnet] (done) | time=11ms
[INFO] Sensor VB.NET Properties [vbnet]
[INFO] Sensor VB.NET Properties [vbnet] (done) | time=0ms
[INFO] Sensor IaC Docker Sensor [iac]
[INFO] 0 source files to be analyzed
[INFO] 0/0 source files have been analyzed
[INFO] Sensor IaC Docker Sensor [iac] (done) | time=47ms
[INFO] ------------- Run sensors on project
[INFO] Sensor Analysis Warnings import [csharp]
[INFO] Sensor Analysis Warnings import [csharp] (done) | time=0ms
[INFO] Sensor Zero Coverage Sensor
[INFO] Sensor Zero Coverage Sensor (done) | time=9ms
[INFO] Sensor Java CPD Block Indexer
[INFO] Sensor Java CPD Block Indexer (done) | time=46ms
[INFO] SCM Publisher SCM provider for this project is: git
[INFO] SCM Publisher 25 source files to be analyzed
[INFO] SCM Publisher 25/25 source files have been analyzed (done) | time=322ms
[INFO] CPD Executor 10 files had no CPD blocks
[INFO] CPD Executor Calculating CPD for 12 files
[INFO] CPD Executor CPD calculation finished (done) | time=7ms
[INFO] Analysis report generated in 50ms, dir size=217.6 kB
[INFO] Analysis report compressed in 34ms, zip size=79.5 kB
[INFO] Analysis report uploaded in 53ms
[INFO] ANALYSIS SUCCESSFUL, you can find the results at: http://211.34.231.84:30262/dashboard?id=edu
[INFO] Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
[INFO] More about the report processing at http://211.34.231.84:30262/api/ce/task?id=AYchJdLGw_Vp_k2kP2ZW
[INFO] Analysis total time: 7.425 s
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  51.927 s
[INFO] Finished at: 2023-03-27T12:38:59+09:00
[INFO] ------------------------------------------------------------------------

SonarQube로 이동하여 데이터를 확인해 봅니다.
Thirdproject 라는 이름으로 하나가 생성된 것을 볼수 있습니다.


프로젝트를 클릭하여 좀 더 자세한 정보를 볼 수 있습니다.


Jenkins Pipeline은 아래와 같습니다.
기존 backend pipeline 에 SonarQube가 추가가 되었습니다.


        ...
        stage('SonarQube Analysis') {
           container('build-tools') {
               withSonarQubeEnv('sonarqube'){ // 시스템설정 값
                 sh "./mvnw clean verify sonar:sonar -Dsonar.projectKey=edu"
             }
           }  
        }
        ...

전체 Pipeline 입니다.


def label = "agent-${UUID.randomUUID().toString()}"
def gitBranch = 'master'
def docker_registry = "ghcr.io"  
def imageName = "ghcr.io/shclub/edu12-backend"
def fromImage = "ghcr.io/shclub/jre17-runtime:v1.0.0"
def git_ops_name = "edu12-backend-gitops"
def P_NAMESPACE = "edu30"

def TAG = getTag(gitBranch)

podTemplate(label: label, serviceAccount: 'jenkins-admin', namespace: P_NAMESPACE,
    containers: [
        containerTemplate(name: 'build-tools', image: 'ghcr.io/shclub/build-tool:v1.0.0', ttyEnabled: true, command: 'cat', privileged: true, alwaysPullImage: true)
        ,containerTemplate(name: 'jnlp', image: 'ghcr.io/shclub/jenkins/jnlp-slave:latest-jdk11', args: '${computer.jnlpmac} ${computer.name}')
    ],
    volumes: [
        hostPathVolume(hostPath: '/etc/containers' , mountPath: '/var/lib/containers' ),
        persistentVolumeClaim(mountPath: '/var/jenkins_home', claimName: 'jenkins-edu-slave-pvc',readOnly: false)
        ]){    
  
    
    node(label) { 
        stage('SCM') {
           checkout scm
       }  
       
        stage('SonarQube Analysis') {
           container('build-tools') {
               withSonarQubeEnv('sonarqube'){ // 시스템설정 값
                 sh "./mvnw clean verify sonar:sonar -Dsonar.projectKey=edu"
             }
           }  
        }
        
       stage('Maven Build & Image Push ') {
            container('build-tools') {
               withCredentials([usernamePassword(credentialsId: 'github_ci',usernameVariable: 'USERNAME',passwordVariable: 'PASSWORD')]) {
                    sh  """
                         ./mvnw clean package jib:build  -Dmaven.test.skip=true  \
                         -Djib.from.image=${fromImage} \
                         -Djib.from.auth.username=${USERNAME} \
                         -Djib.from.auth.password=${PASSWORD} \
                         -Djib.to.image=${imageName} \
                         -Djib.to.tags=${TAG}  \
                         -Djib.to.auth.username=${USERNAME} \
                         -Djib.to.auth.password=${PASSWORD}
                         echo 'TAG ==========> ' ${TAG}
                   """
              }
            }
        }

        stage('GitOps update') {
            container('build-tools') {
               withCredentials([usernamePassword(credentialsId: 'github_ci',usernameVariable: 'USERNAME',passwordVariable: 'PASSWORD')]) {
                    sh """  
                        cd ~
                        git clone https://${USERNAME}:${PASSWORD}@github.com/${USERNAME}/${git_ops_name}
                        cd ${git_ops_name}
                        git checkout HEAD
                        kustomize edit set image ${imageName}:${TAG}
                        git config --global user.email "shclub@gmail.com"
                        git config --global user.name ${USERNAME}
                        git add .
                        git commit -am 'update image tag  ${TAG} from My_Jenkins'
                        cat kustomization.yaml
                        git push origin HEAD
                    """
               } 
            }
        }
        
        
    }
}

def getTag(branchName){     
    def TAG
    def DATETIME_TAG = new Date().format('yyyyMMddHHmmss')
    TAG = "${DATETIME_TAG}"
    return TAG
}