Prerequisites
A functional Kubernetes cluster with Traefik installed as the ingress controller. Network File System (NFS) storage mounted across all nodes to ensure Jenkins configuration survives pod restarts. A private Docker registry accessible at 192.168.0.153:5000. The base environment uses an Alpine LNP stack (PHP 5.6.31 and Nginx 1.8.1) available as liuyusheng/alpine-lnp:v1.
Jenkins Master Deployment
Deploy Jenkins using a persistent volume backed by NFS:
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins-ci
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
tier: automation-server
template:
metadata:
labels:
tier: automation-server
spec:
nodeSelector:
workload: ci-cd
containers:
- name: jenkins-master
image: jenkins/jenkins:alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: ui
protocol: TCP
- containerPort: 50000
name: jnlp
protocol: TCP
volumeMounts:
- name: persistent-data
mountPath: /var/jenkins_home
volumes:
- name: persistent-data
nfs:
server: 192.168.0.161
path: /data/jenkins
---
apiVersion: v1
kind: Service
metadata:
name: jenkins-svc
spec:
ports:
- port: 8080
targetPort: 8080
name: http
- port: 50000
targetPort: 50000
name: slave
selector:
tier: automation-server
Ingress Resource
Expose Jenkins through Traefik:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jenkins-ingress
namespace: default
spec:
rules:
- host: ci.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: jenkins-svc
port:
number: 8080
Deploy with kubectl apply -f <filename>.
Pipeline Configuration
Configure Jenkins with SSH credentials for remote host authentication. The following shell script executes within Jenkins job contexts:
#!/bin/bash
REMOTE_HOST="192.168.0.153"
SSH_USER="root"
SSH_PORT="22"
TEAM_NAME="lin_lang"
APP_NAME="fabaoguo"
STAGING_PATH="/data/tmp/"
JENKINS_ROOT="/var/jenkins_home"
ARTIFACT="${APP_NAME}.tar.gz"
REGISTRY_ENDPOINT="192.168.0.153:5000"
VHOST_FILE="www.${APP_NAME}.com.conf"
SOURCE_IMAGE="alpine:v5"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
DOMAIN_NAME="www.fabaoguo.com"
# Prepare workspace
mkdir -p ${JENKINS_ROOT}/artifacts
tar -czf ${JENKINS_ROOT}/artifacts/${ARTIFACT} --exclude=.svn --exclude=artifacts .
# Generate Nginx configuration
cp ${JENKINS_ROOT}/scripts/vhost-template.conf ${JENKINS_ROOT}/scripts/${VHOST_FILE}
bash ${JENKINS_ROOT}/scripts/configure-vhost.sh ${JENKINS_ROOT}/scripts/${VHOST_FILE} ${APP_NAME}
# Transfer assets
scp -P ${SSH_PORT} ${JENKINS_ROOT}/artifacts/${ARTIFACT} ${SSH_USER}@${REMOTE_HOST}:${STAGING_PATH}
scp -P ${SSH_PORT} ${JENKINS_ROOT}/scripts/${VHOST_FILE} ${SSH_USER}@${REMOTE_HOST}:${STAGING_PATH}
# Trigger build and deployment
ssh -p ${SSH_PORT} ${SSH_USER}@${REMOTE_HOST} "bash /data/scripts/create-image.sh ${ARTIFACT} ${VHOST_FILE} ${SOURCE_IMAGE} ${REGISTRY_ENDPOINT}/${APP_NAME} ${TIMESTAMP}"
ssh -p ${SSH_PORT} ${SSH_USER}@${REMOTE_HOST} "bash /data/scripts/rollout-app.sh ${DOMAIN_NAME} ${APP_NAME} ${APP_NAME} ${TIMESTAMP} ${APP_NAME}.yaml"
# Cleanup
rm -f ${JENKINS_ROOT}/artifacts/${ARTIFACT}
rm -f ${JENKINS_ROOT}/scripts/${VHOST_FILE}
Image Build Process
On the build server (192.168.0.153), create the container image:
#!/bin/bash
ARCHIVE=$1
VHOST_CONFIG=$2
BASE_LAYER=$3
OUTPUT_IMAGE=$4
VERSION_TAG=$5
mkdir -p /data/build-context
if [[ -z "$ARCHIVE" || -z "$VHOST_CONFIG" || -z "$BASE_LAYER" || -z "$OUTPUT_IMAGE" || -z "$VERSION_TAG" ]]; then
echo "Error: Missing required parameters"
exit 1
fi
COPY_CODE="COPY ${ARCHIVE} /opt/www/"
COPY_CONFIG="COPY ${VHOST_CONFIG} /etc/nginx/conf.d/"
cat > /data/build-context/Dockerfile <<EOF
FROM ${BASE_LAYER}
MAINTAINER ops-team
ENV REFRESHED_AT $(date +%Y-%m-%d)
${COPY_CODE}
${COPY_CONFIG}
EOF
docker build --no-cache -t ${OUTPUT_IMAGE}:${VERSION_TAG} /data/build-context/
docker push ${OUTPUT_IMAGE}:${VERSION_TAG}
echo "Published: ${OUTPUT_IMAGE}:${VERSION_TAG}"
rm -rf /data/build-context/*
Aplication Deployment Template
The Kubernetes manifest template for PHP applications:
apiVersion: v1
kind: Service
metadata:
name: php-app-svc
spec:
ports:
- port: 80
targetPort: 80
selector:
app: php-application
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app
spec:
replicas: 1
minReadySeconds: 60
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 50%
maxUnavailable: 0
selector:
matchLabels:
app: php-application
template:
metadata:
labels:
app: php-application
spec:
containers:
- name: php-runtime
image: 192.168.0.153:5000/sample-app:latest
imagePullPolicy: Always
ports:
- containerPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: php-app-ingress
spec:
rules:
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: php-app-svc
port:
number: 80
Deployment Orchestration Script
#!/bin/bash
# rollout-app.sh
HOST_HEADER=$1
PROJECT_ID=$2
IMAGE_REPO=$3
RELEASE_ID=$4
MANIFEST_NAME=$5
TEMPLATE_DIR="/data/scripts"
TARGET_DIR="${TEMPLATE_DIR}/releases"
if [[ -z "$HOST_HEADER" || -z "$PROJECT_ID" || -z "$IMAGE_REPO" || -z "$RELEASE_ID" || -z "$MANIFEST_NAME" ]]; then
echo "Error: Insufficient arguments provided"
exit 1
fi
mkdir -p ${TARGET_DIR}
cp ${TEMPLATE_DIR}/templates/app-template.yaml ${TARGET_DIR}/${MANIFEST_NAME}
sed -i "s/{{PROJECT}}/${PROJECT_ID}/g" ${TARGET_DIR}/${MANIFEST_NAME}
sed -i "s/{{IMAGE}}/${IMAGE_REPO}/g" ${TARGET_DIR}/${MANIFEST_NAME}
sed -i "s/{{VERSION}}/${RELEASE_ID}/g" ${TARGET_DIR}/${MANIFEST_NAME}
sed -i "s/{{DOMAIN}}/${HOST_HEADER}/g" ${TARGET_DIR}/${MANIFEST_NAME}
kubectl apply -f ${TARGET_DIR}/${MANIFEST_NAME} --record
Verification
Check pod status:
kubectl get pods -o wide
Expected output shows the running applicatino pod with IP address and node assignment.