DevOps with Terraform, ArgoCD, Jenkins and AWS

Mô hình

AWS Configure
aws configure

Install Jenkins using Terraform
Clone code từ repo trên github:
git clone https://github.com/HoangPhan10/Teris.git
cd Teris
cd jenkins-terraform
Danh sách file:

Tạo resource Security group cho server jenkins, mở các port 22, 88, 443, 8080, 9000, 3000:

Tạo resource EC2 và cấp Role AdministratorAccess cho máy chủ jenkins:

Tệp Terraform để triển khai AWS EC2 với Jenkins được cài đặt, cùng với container SonarQube, Trivy, AWS CLI, Kubectl và Terraform.
#!/bin/bash
sudo apt update -y
wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | tee /etc/apt/keyrings/adoptium.asc
echo "deb [signed-by=/etc/apt/keyrings/adoptium.asc] https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main" | tee /etc/apt/sources.list.d/adoptium.list
sudo apt update -y
sudo apt install temurin-17-jdk -y
/usr/bin/java --version
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee /usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian-stable binary/ | sudo tee /etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt-get update -y
sudo apt-get install jenkins -y
sudo systemctl start jenkins
sudo systemctl status jenkins
#install docker
sudo apt-get update
sudo apt-get install docker.io -y
sudo usermod -aG docker ubuntu
newgrp docker
sudo chmod 777 /var/run/docker.sock
docker run -d --name sonar -p 9000:9000 sonarqube:lts-community
# install trivy
sudo apt-get install wget apt-transport-https gnupg lsb-release -y
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy -y
#install terraform
sudo apt install wget -y
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
#install Kubectl on Jenkins
sudo apt update
sudo apt install curl -y
curl -LO https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --client
#install Aws cli
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
sudo apt-get install unzip -y
unzip awscliv2.zip
sudo ./aws/install
Mở terminal và chạy các lệnh sau:
terraform init

Validate các file terraform:
$ terraform validate
Success! The configuration is valid.
Plan tạo các resources:
terraform plan

Sau đó, apply các resources và nhập yes ở bước confirm:
terraform apply --auto-approve

Các state của terraform sẽ được lưu trữ tại bucket s3 s3-teris-v1


Quá trình terraform apply đã hoàn tất. Bây giờ, bạn có thể vào AWS Console để kiểm tra EC2 instance đã được tạo thành công:

Truy cập và cấu hình Jenkins
Trong file install_jenkins.sh thì chúng ta đã thực hiện chạy jenkins, để truy cập vào sử dụng public_ip_ec2 với port 8080:

Sử dụng keypair của riêng bạn lúc tạo ec2 để truy cập vào server:
ssh -i ./keypair/keypair ubuntu@3.92.21.20

Để lấy được mật khẩu administrator , thực hiện đọc tệp file /var/lib/jenkins/secrets/initialAdminPassword:
sudo cat /var/lib/jenkins/secrets/initialAdminPassword

Tiếp đến, Install suggested plugins

Jenkins sẽ thực hiện cài đặt một số plugin cần thiết:

Bây giờ thực hiện tạo user Admin:

Giao diện Dashboard Jenkins:

Dashboard Jenkins → Manage Jenkins → Plugins
Chọn đến Available plugins:

Tìm kiếm và tải xuống Terraform:

Tiếp đến, truy cập vào server jenkins để lấy path của terraform với lệnh:
which terraform

Dashboard Jenkins → Manage Jenkins → Tools
Thêm terraform vào tools:

Go to DashBoard Jenkins → Manage Jenkins → Plugins → Available Plugins
Trước tiên, chúng ta cần cài đặt một số plugin:
Eclipse Temurin installer: Thao tác với JDK
Sonarqube Scanner: Phân tích mã nguồn để phát hiện các lỗi và lỗ hổng.
NodeJs: Thao tác với Node
Owasp Dependency-Check: Phân tích các thư viện được sử dụng với các CVE.
Docker, Docker Commons, Docker Pipeline, Docker API, Docker-build-step: Thao tác với docker

Goto Manage Jenkins → Tools
Cấu hình các Tools cho Jenkins:
Thêm JDK jdk17:

Thêm NodeJS node16:

Thêm Sonar Scanner sonar-scanner:

Thêm Dependency-Check DP-Check:

Thêm Docker docker:

Truy cập vào SonarQube với port 9000:

Login: admin
Password: admin
Goto Administration → Security → Users:

Chọn Update Tokens:

Tạo token mới và copy token này:

Goto Jenkins Dashboard → Manage Jenkins → Credentials → System → Global credentials
Tạo 1 credentials mới sử dụng secret sonarqube vừa rồi:

Tạo thêm 1 credentials cho docker:

Goto Dashboard → Manage Jenkins → System
Thêm Sonar Server sonar-server sử dụng credentials vừa tạo:

Cấu hình webhooks ở SonarQube:

Tạo webhook sử dụng url của server jenkins và thêm /sonarqube-webhook/:

Tạo job để tạo EKS bằng Terraform
Tạo job với type Pipeline:

Tại đây thì khi tạo eks, chúng ta cần thêm 1 số action như apply, destroy để phù hợp. Nếu muốn xóa bỏ resource eks thì chỉ cần chạy job với action destroy.

Sau đó, thêm script pipeline sau:
pipeline{
agent any
stages {
stage('Checkout from Git'){
steps{
git branch: 'master', url: 'https://github.com/HoangPhan10/Teris.git'
}
}
stage('Terraform version'){
steps{
sh 'terraform --version'
}
}
stage('Terraform init'){
steps{
dir('eks-terraform') {
sh 'terraform init'
}
}
}
stage('Terraform validate'){
steps{
dir('eks-terraform') {
sh 'terraform validate'
}
}
}
stage('Terraform plan'){
steps{
dir('eks-terraform') {
sh 'terraform plan'
}
}
}
stage('Terraform apply/destroy'){
steps{
dir('eks-terraform') {
sh 'terraform ${action} --auto-approve'
}
}
}
}
}
Thực hiện Build job với param apply:

Mất khoảng hơn 10 phút để chạy xong job này, vì tạo eks sẽ hơi mất thời gian:

Kiểm tra AWS Console của resource EKS:

EKS tạo thêm 2 máy chủ EC2 để làm worker node:

Tạo job để build, push image lên DockerHub
Tạo job với type Pipeline:

Sau đó, thêm script pipeline sau:
pipeline {
agent any
tools {
jdk 'jdk17'
nodejs 'node16'
}
environment {
SCANNER_HOME = tool 'sonar-scanner'
}
stages {
stage('clean workspace') {
steps {
cleanWs()
}
}
stage('Checkout from Git') {
steps {
git branch: 'master', url: 'https://github.com/HoangPhan10/Tetris.git'
}
}
stage('Sonarqube Analysis') {
steps {
dir('code') {
withSonarQubeEnv('sonar-server') {
sh ''' $SCANNER_HOME/bin/sonar-scanner -Dsonar.projectName=Tetris \
-Dsonar.projectKey=Tetris '''
}}
}
}
stage('quality gate') {
steps {
dir('code') {
script {
waitForQualityGate abortPipeline: false, credentialsId: 'Sonar-token'
}}
}
}
stage('Install Dependencies') {
steps {
dir('code') {
sh 'npm install'}
}
}
stage('OWASP FS SCAN') {
steps {
dir('code') {
dependencyCheck additionalArguments: '--scan ./ --disableYarnAudit --disableNodeAudit', odcInstallation: 'DP-Check'
dependencyCheckPublisher pattern: '**/dependency-check-report.xml'
}}
}
stage('TRIVY FS SCAN') {
steps {
sh 'trivy fs . > trivyfs.txt'
}
}
stage('Docker Build & Push') {
steps {
dir('code') {
script {
withDockerRegistry(credentialsId: 'Docker', toolName: 'docker') {
sh 'docker build -t tetris .'
sh 'docker tag tetris phanhoang102/tetris:latest '
sh 'docker push phanhoang102/tetris:latest '
}
}}
}
}
stage('TRIVY') {
steps {
sh 'trivy image phanhoang102/tetris:latest > trivyimage.txt'
}
}
}
}
Sau đó Apply và Build job:

Ở SonarQube, nó đã thực hiện quét 1.1k dòng và phát hiện được 2 bug:

Owasp Dependency Check:

Đẩy thành công image lên DockerHub:

Cập nhật image lên argocd
Goto Github → Setting, tạo token account github.
Chọn Developer settings:

Chọn Tokens (classic):

Tạo token mới:

Đặt tên và chọn tất cả các checkbox:

Copy token:

Thêm credential của github vào jenkins:

Thêm stage vào script pipeline:
#add inside environment
environment {
GIT_REPO_NAME = "Tetris-argocd"
GIT_USER_NAME = "HoangPhan10"
}
# add these stages after trivy image scan
stage('Checkout Code') {
steps {
git branch: "master", url: "https://github.com/HoangPhan10/Tetris-argocd.git"
}
}
stage('Update Deployment File') {
steps {
script {
withCredentials([string(credentialsId: "Github", variable: "GITHUB_TOKEN")]) {
NEW_IMAGE_NAME = "phanhoang102/tetris:${env.VERSION_ID}"
sh "sed -i 's|image: .*|image: $NEW_IMAGE_NAME|' deployment.yaml"
sh 'git add deployment.yaml'
sh "git commit -m 'Update deployment image to $NEW_IMAGE_NAME'"
sh "git push https://${GITHUB_TOKEN}@github.com/${GIT_USER_NAME}/${GIT_REPO_NAME} HEAD:master"
}
}
}
}
Tiếp đến cập nhật vào với script đã có:
pipeline {
agent any
tools {
jdk "jdk17"
nodejs "node16"
}
environment {
SCANNER_HOME = tool "sonar-scanner"
GIT_REPO_NAME = "Tetris-argocd"
GIT_USER_NAME = "HoangPhan10"
}
stages {
stage("Clean workspace") {
steps {
cleanWs()
}
}
stage("Initialize") {
steps {
script {
env.VERSION_ID = "v1.${env.BUILD_NUMBER}"
echo "Version ID: ${env.VERSION_ID}"
}
}
}
stage("Checkout from Git") {
steps {
git branch: "master", url: "https://github.com/HoangPhan10/Tetris.git"
}
}
stage("Sonarqube Analysis") {
steps {
dir("code") {
withSonarQubeEnv("sonar-server") {
sh ''' $SCANNER_HOME/bin/sonar-scanner -Dsonar.projectName=Tetris \
-Dsonar.projectKey=Tetris '''
}}
}
}
stage("Quality gate") {
steps {
dir("code") {
script {
waitForQualityGate abortPipeline: false, credentialsId: "Sonar-token"
}}
}
}
stage("Install Dependencies") {
steps {
dir("code") {
sh "npm install"}
}
}
stage("OWASP FS SCAN") {
steps {
dir("code") {
dependencyCheck additionalArguments: "--scan ./ --disableYarnAudit --disableNodeAudit", odcInstallation: "DP-Check"
dependencyCheckPublisher pattern: "**/dependency-check-report.xml"
}}
}
stage("TRIVY FS SCAN") {
steps {
sh "trivy fs . > trivyfs.txt"
}
}
stage("Build Docker Image") {
steps {
dir("code") {
script {
sh "docker build -t tetris:${env.VERSION_ID} ."
}
}
}
}
stage("Push Docker Image") {
steps {
script {
withDockerRegistry(credentialsId: "Docker") {
sh "docker tag tetris:${env.VERSION_ID} phanhoang102/tetris:${env.VERSION_ID}"
sh "docker push phanhoang102/tetris:${env.VERSION_ID}"
sh "docker rmi tetris:${env.VERSION_ID} phanhoang102/tetris:${env.VERSION_ID}"
}
}
}
}
stage("TRIVY") {
steps {
sh "trivy image phanhoang102/tetris:${env.VERSION_ID} > trivyimage.txt"
}
}
stage("Checkout Code") {
steps {
git branch: "master", url: "https://github.com/HoangPhan10/Tetris-argocd.git"
}
}
stage("Update Deployment File") {
steps {
script {
withCredentials([string(credentialsId: "Github", variable: "GITHUB_TOKEN")]) {
NEW_IMAGE_NAME = "phanhoang102/tetris:${env.VERSION_ID}"
sh "sed -i 's|image: .*|image: $NEW_IMAGE_NAME|' deployment.yaml"
sh 'git add deployment.yaml'
sh "git commit -m 'Update deployment image to $NEW_IMAGE_NAME'"
sh "git push https://${GITHUB_TOKEN}@github.com/${GIT_USER_NAME}/${GIT_REPO_NAME} HEAD:master"
}
}
}
}
}
}
Apply và Build job:

Tạo commit change image lên github dùng để chạy argocd:

Setup ArgoCD
Cập nhật file kubeconfig trên server jenkins:
aws eks update-kubeconfig --name <CLUSTER NAME> --region <CLUSTER REGION>
aws eks update-kubeconfig --name EKS_CLOUD --region ap-south-1

Xem các nodes:
kubectl get nodes

Cài đặt ArgoCD:
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.4.7/manifests/install.yaml

Theo mặc định thì server argocd không public internet, chúng ta cần cập nhật sang dạng loadbalancer:
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'

Lấy EXTERNAL-IP của service:
kubectl get svc argocd-server -n argocd

Truy cập vào server argocd:

Lấy mật khẩu argocd:
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && printf "\n"

Giao diện màn hình argocd:

Tiếp đến, chọn Setting ở thanh navbar bên trái:

Chọn tiếp vào Repositories:

Chọn Connect repo using HTTPS:

Chọn type git, project default và nhập đường dẫn github vào:

Sau đó click Connect:

Bây giờ quay về trang chủ, chọn Create Application:

Nhập các thông tin cần thiết để tạo app:

Sử dụng ./ để sử dụng thư mục gốc:

Khai báo chạy trên cluster và namespace nào:

Sau đó chọn Create, tạo thành công:

Argocd đã thực hiện tạo ra các resource k8s mà chúng ta đã khởi tạo trong file trên github:

Chọn Details của svc để lấy link truy cập dịch vụ:


Triển khai thành công:

Tạo thêm job chạy app version 2
Tạo job với type pipeline:

Với script pipeline tương tự như version1, thay đổi 1 số thông tin:
pipeline {
agent any
tools {
jdk "jdk17"
nodejs "node16"
}
environment {
SCANNER_HOME = tool "sonar-scanner"
GIT_REPO_NAME = "Tetris-argocd"
GIT_USER_NAME = "HoangPhan10"
}
stages {
stage("Clean workspace") {
steps {
cleanWs()
}
}
stage("Initialize") {
steps {
script {
env.VERSION_ID = "v1.${env.BUILD_NUMBER}"
echo "Version ID: ${env.VERSION_ID}"
}
}
}
stage("Checkout from Git") {
steps {
git branch: "master", url: "https://github.com/HoangPhan10/Tetris-v2.git"
}
}
stage("Sonarqube Analysis") {
steps {
withSonarQubeEnv("sonar-server") {
sh ''' $SCANNER_HOME/bin/sonar-scanner -Dsonar.projectName=Tetrisv2 \
-Dsonar.projectKey=Tetrisv2 '''
}
}
}
stage("Quality gate") {
steps {
script {
waitForQualityGate abortPipeline: false, credentialsId: "Sonar-token"
}
}
}
stage("Install Dependencies") {
steps {
sh "npm install"
}
}
stage("OWASP FS SCAN") {
steps {
dependencyCheck additionalArguments: "--scan ./ --disableYarnAudit --disableNodeAudit", odcInstallation: "DP-Check"
dependencyCheckPublisher pattern: "**/dependency-check-report.xml"
}
}
stage("TRIVY FS SCAN") {
steps {
sh "trivy fs . > trivyfs.txt"
}
}
stage("Build Docker Image") {
steps {
script {
sh "docker build -t tetrisv2:${env.VERSION_ID} ."
}
}
}
stage("Push Docker Image") {
steps {
script {
withDockerRegistry(credentialsId: "Docker") {
sh "docker tag tetrisv2:${env.VERSION_ID} phanhoang102/tetrisv2:${env.VERSION_ID}"
sh "docker push phanhoang102/tetrisv2:${env.VERSION_ID}"
sh "docker rmi tetrisv2:${env.VERSION_ID} phanhoang102/tetrisv2:${env.VERSION_ID}"
}
}
}
}
stage("TRIVY") {
steps {
sh "trivy image phanhoang102/tetrisv2:${env.VERSION_ID} > trivyimage.txt"
}
}
stage("Checkout Code") {
steps {
git branch: "master", url: "https://github.com/HoangPhan10/Tetris-argocd.git"
}
}
stage("Update Deployment File") {
steps {
script {
withCredentials([string(credentialsId: "Github", variable: "GITHUB_TOKEN")]) {
NEW_IMAGE_NAME = "phanhoang102/tetrisv2:${env.VERSION_ID}"
sh "sed -i 's|image: .*|image: $NEW_IMAGE_NAME|' deployment.yaml"
sh 'git add deployment.yaml'
sh "git commit -m 'Update deployment image to $NEW_IMAGE_NAME'"
sh "git push https://${GITHUB_TOKEN}@github.com/${GIT_USER_NAME}/${GIT_REPO_NAME} HEAD:master"
}
}
}
}
}
}
Lúc này thì sẽ tạo ra một repo image mới trên DockerHub:

Sau đó, cập nhật lên github chứa file chạy k8s:

Argocd server sẽ tự động cập nhật lại khi có commit change trên git:

Đã được cập nhật sang image version 2:

Truy cập lại vào app thì lúc này đã được chuyển sang version 2:

Thông báo kết quả build về mail
Goto Dashboard → Manage Jenkins → Plugins
Chọn Available plugins, tìm kiếm Email Extension Template và tải xuống:

Goto Gmail → Quản lý tài khoản
Tìm kiếm và chọn mật khẩu ứng dụng:

Tạo một ứng dụng mới và copy mật khẩu:

Goto Dashboard → Manage Jenkins → Credentials
Thêm credential cho mail bằng mật khẩu vừa tạo:

Goto Dashboard → Manage Jenkins → System
Cấu hình Extended E-mail Notification:

Cấu hình E-mail Notification:

Sau đó, thêm script sau vào:
post {
always {
emailext attachLog: true,
subject: "'${currentBuild.result}'",
body: "Project: ${env.JOB_NAME}<br/>" +
"Build Number: ${env.BUILD_NUMBER}<br/>" +
"URL: ${env.BUILD_URL}<br/>",
to: 'phanhoang1022002@gmail.com',
attachmentsPattern: 'trivyfs.txt,trivyimage.txt'
}
}
Sau khi build nhận được kết quả gửi về mail:

Tạo job để trigger
Tạo job với type pipeline:

Cấu hình parameter:

Chạy script sau:
pipeline{
agent any
environment {
GIT_REPO_NAME = "Tetris-argocd"
GIT_USER_NAME = "HoangPhan10"
}
stages {
stage("Checkout Code") {
steps {
git branch: "master", url: "https://github.com/HoangPhan10/Tetris-argocd.git"
}
}
stage("Update Deployment File") {
steps {
script {
withCredentials([string(credentialsId: "Github", variable: "GITHUB_TOKEN")]) {
NEW_IMAGE_NAME = "${image}"
sh "sed -i 's|image: .*|image: $NEW_IMAGE_NAME|' deployment.yaml"
sh 'git add deployment.yaml'
sh "git commit -m 'Update deployment image to $NEW_IMAGE_NAME'"
sh "git push https://${GITHUB_TOKEN}@github.com/${GIT_USER_NAME}/${GIT_REPO_NAME} HEAD:master"
}
}
}
}
}
}
Thực hiện thay thế vào job chạy app version 1 và version 2:
// stage("Checkout Code") {
// steps {
// git branch: "master", url: "https://github.com/HoangPhan10/Tetris-argocd.git"
// }
// }
// stage("Update Deployment File") {
// steps {
// script {
// withCredentials([string(credentialsId: "Github", variable: "GITHUB_TOKEN")]) {
// NEW_IMAGE_NAME = "phanhoang102/tetris:${env.VERSION_ID}"
// sh "sed -i 's|image: .*|image: $NEW_IMAGE_NAME|' deployment.yaml"
// sh 'git add deployment.yaml'
// sh "git commit -m 'Update deployment image to $NEW_IMAGE_NAME'"
// sh "git push https://${GITHUB_TOKEN}@github.com/${GIT_USER_NAME}/${GIT_REPO_NAME} HEAD:master"
// }
// }
// }
// }
stage('Trigger manifest') {
steps {
build job: 'Manifest',
wait: true,
parameters: [
string(name: 'image', value: "phanhoang102/tetris:${env.VERSION_ID}")
]
}
}
Kết quả sau khi chạy:

Xóa các resource
Đầu tiên, xóa app ở trên argocd.
Goto ArgoCD, chọn DELETE:

Xác nhận xóa app:


Tiếp đến thực hiện xóa server argocd.
Thực hiện truy cập SSH vào server jenkins để tương tác với cụm k8s:
kubectl delete svc argocd-server -n argocd

Bước 3, thực hiện xóa eks bằng cách chạy job với parameter destroy:


Cuối cùng, thực hiện xóa jenkins bằng terraform:
terraform destroy --auto-approve


Cảm ơn bạn đã đọc.




