How to Build a Stable Multi-Environment Deployment Pipeline with Helm and Kubernetes
This article walks through a real‑world case of a SpringBoot service whose message routing failed due to missing configuration, then demonstrates how to redesign the delivery workflow using a single Docker image, unified application.yaml, Helm charts, and CI/CD scripts to achieve environment‑agnostic, reliable deployments across development, staging, and production.
A Troubled Deployment
Wang Bo, responsible for the Cloud Efficient AppStack delivery, describes a scenario where a SpringBoot service failed to send messages in the staging environment because the routing.env property was missing from application‑staging.yaml. The service used environment‑specific tags (daily, staging, production‑hangzhou) to route messages, and the missing configuration caused the messages to be dropped.
package com.example.demo.service;
import com.example.demo.model.BrilliantInfo;
import io.coolmq.sdk.MessageSender;
import io.coolmq.sdk.model.TaggedMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class CoolMessageDispatchServiceImpl extends CoolMessageDispatchService {
// 环境路由tag:
// 日常: daily
// 预发: staging
// 杭州区域生产: production-hangzhou
@Value("${routing.env}")
private String environmentIdentifier;
@Autowired
private BrilliantInfoService brilliantInfoService;
@Override
public String saySomething(String brilliantIdentifier) {
// 使用酷炫的入参查询业务信息
BrilliantInfo brilliantInfo = brilliantInfoService.query(brilliantIdentifier);
// 基于业务信息拼装字符串消息体
String messageContent = "Yes, indeed - 'tis called " + Optional.ofNullable(brilliantInfo).map(BrilliantInfo::getName).orElse("oops");
// 构造消息对象并打tag, 保证只有监听对应tag的消费方能收到消息
TaggedMessage messageWrapped = TaggedMessage.builder().content(messageContent).tag(environmentIdentifier);
// 请原谅阿伟,他有点急,调SDK发消息的时候没有捕获异常打日志
MessageSender.send(messageWrapped);
}
}Problems with Traditional Deployment
Using separate application.yaml files for each environment and different Dockerfiles introduces manual diff work, risk of missing configuration, and divergent artifacts that can cause subtle bugs across environments.
Environment‑specific configuration must be manually verified, increasing the chance of errors.
Dockerfiles often differ only in environment variables, yet each build produces a distinct image, making reproducibility hard.
Dependency version drift between builds can reduce confidence in test results.
Considerations for a Single‑Image, Multi‑Environment Deployment
Make the artifact environment‑neutral; inject configuration at deployment time.
Use a unified configuration template (single application.yaml) with placeholders.
Keep a single Docker image for daily, staging, and production.
Step 1: Consolidate application.yaml and Dockerfile
Keep only one application.yaml that contains a placeholder for the routing tag:
# 这里只展示了阿伟关心的路由配置
routing:
env: ${DEPLOY_ENV}The Dockerfile is stripped of environment‑specific ENV statements and assumes the necessary variables are provided at runtime.
Step 2: Write a Helm Chart
Initialize an empty Helm chart and add the needed templates.
# 用helm初始化模板目录
helm create cool-service-chart
# 把不需要的文件删掉
rm -rf cool-service-chart/templates/*
rm -f cool-service-chart/values.yamlExample deployment.yaml template:
# cool-service-chart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-deployment
labels:
app: {{ .Release.Name }}
spec:
selector:
matchLabels:
app: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
spec:
containers:
- name: main
image: {{ .Values.image }}
envFrom:
- configMapRef:
name: {{ .Release.Name }}-configConfigMap for environment variables:
# cool-service-chart/templates/envs-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-config
data:
DEPLOY_ENV: {{ .Values.deployEnv }}Step 3: Generate Values.yaml in CI/CD
A Jenkins pipeline can build the Docker image, set DEPLOY_ENV_VALUE, and run a script to produce the Helm values file.
pipeline {
agent any
stages {
stage('Unified Build') {
// Build and push Docker image, store URL in DOCKER_IMAGE_URL
}
stage('Deploy to Daily') {
environment {
DEPLOY_ENV_VALUE = "daily"
}
sh "chart-complete.sh"
sh "deploy-to-env.sh"
}
stage('Staging Approval') {
input "Deploy to staging?"
}
stage('Deploy to Staging') {
environment {
DEPLOY_ENV_VALUE = "staging"
}
sh "chart-complete.sh"
sh "deploy-to-env.sh"
}
// Production stage omitted for brevity
}
}The chart-complete.sh script clones the chart repository, creates values.yaml, and packages the chart:
#!/bin/sh
# 克隆chart
git clone [email protected]:cool-group/cool-service-chart.git
# 生成values.yaml
touch cool-service-chart/values.yaml
echo "image: $DOCKER_IMAGE_URL" >> cool-service-chart/values.yaml
echo "deployEnv: $DEPLOY_ENV_VALUE" >> cool-service-chart/values.yaml
# 打包chart
tar -cvzf cool-service-chart.tgz ./cool-service-chartConclusion
By unifying the Docker image and deferring environment‑specific configuration to deployment time via Helm and CI/CD scripts, teams can eliminate configuration drift, reduce manual errors, and achieve reliable, repeatable deployments across all stages. The approach leverages cloud‑native IaC principles to keep the codebase identical in every environment, improving integration confidence and testing efficiency.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
