Automated PHP Deployment Pipeline with Jenkins and Kubernetes

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.

Tags: Jenkins kubernetes PHP nginx CI/CD

Posted on Tue, 09 Jun 2026 16:13:28 +0000 by chris1