Greatly improve project deployment productivity! Share a semi-automated CICD implementation solution!
Semi-automatic CI/CD? Sounds like a compromise compared to a fully automated solution. Using scripts for one-click deployment can indeed save time, but you'll have to tolerate issues like accessing the server's intranet. In the domestic environment, CI/CD always requires finding your own solutions.
目录10 个章节
Preface
A fully automated CI/CD is truly great. After code submission, it automatically builds and releases new versions, enabling rolling updates without downtime and the ability to roll back at any time—who wouldn't like that?
However, ideals are often much more optimistic than reality. Not all development or production environments are equipped to deploy CI/CD.
To get straight to the point, these CI/CD services have some issues: either the network is inaccessible, or they are too heavy and cumbersome to deploy under the given conditions (servers are all on the internal network and cannot connect directly).
That's why during my work, I "innovated" a practical alternative to CI/CD—a semi-automated approach that achieves one-click deployment with a single script!
PS: Due to space constraints, the full code cannot be included in this article. If needed, you can reply "半自动 CICD 脚本" (Semi-Automated CI/CD Script) in the backend of the official account to get it.
About CI/CD
Nowadays, common CI/CD services all have certain barriers. Let's discuss:
- Github Actions: Best suited for open-source projects, no deployment configuration required, completely free 👍 However, it often cannot be used in production environments due to network issues.
- GitLab CI/CD: Quite heavy, requires deploying and configuring GitLab services.
- Jenkins: Quite heavy, requires deploying and configuring Jenkins services.
- Azure DevOps and AWS CodePipeline: These two depend on their respective cloud services, and both are foreign, so basically, they are not worth considering.
Beyond these common ones, there are also some other less mainstream options, which we will also take a look at:
- Domestic Gitee Pipeline: This is similar to Github Actions but is paid. We're just working a job—should we have to pay out of our own pockets to go to work? Pass on this directly.
- CircleCI: A cloud-native CI/CD service offering integration with GitHub and Bitbucket. It should work great abroad, but the domestic network environment certainly wouldn't allow it.
- Bitbucket Pipelines: Closely integrated with Bitbucket repositories, suitable for teams using Bitbucket for code hosting. Similar to the Github situation, no need to consider it.
PS:
Sometimes one can't help but sigh, as if the domestic and foreign environments are two different worlds...
Besides these, I also found a lightweight open-source CI/CD project: https://github.com/flowci/flow-core-x
This one looks promising. I feel it could be used on a HomeLab or NAS. I'll give it a try sometime.
Solution
My solution uses "scripts + Docker" to achieve one-click deployment, zero-downtime updates, and version rollback at any time~
The basic idea is illustrated in the simple diagram below for easier understanding:
graph LR
A([Local])-->B1(Create TAG)
A-->B2(docker build)
B2-- Push image -->C[(Image Registry)]
A-- SSH connection -->D{Server}
C-- Pull image -->D
D-- Start -->E([Online Service])
This solution only requires simple configuration, after which you can deploy with one click. That's why I call it "semi-automatic CICD."
The principle is to tag the local git repository with a version (e.g., v0.0.1). Then, the script automatically identifies this version tag, builds the image with the same tag, and pushes it to a remote image registry. On the server, the image is pulled and started—that's it~ (Simple and straightforward!)
How to Use
The prerequisites for using this approach are:
- Code management using git
- Project deployment using docker
- A private docker image registry is required. This can be self-hosted or use private image services like Alibaba Cloud (阿里云) which are free
- The server must have access to the docker image registry (can be self-hosted in an internal network environment)
Modifying the compose configuration
Before using the script, a small configuration is needed, which will then boost your productivity~
Using a project based on the DjangoStarter framework as an example.
The compose.yaml configuration file
services:
# Omit unrelated content
app:
image: ${APP_IMAGE_NAME}:${APP_IMAGE_TAG}
container_name: $APP_NAME-app
.env environment variables
APP_PORT=9876
APP_NAME=meta-hub
APP_IMAGE_NAME=meta-hub
APP_IMAGE_TAG=v0.0.2
The script will automatically modify the version APP_IMAGE_TAG in the .env file when it runs.
Creating tags
After completing local development
Use the git tag feature to add a version tag to a commit.
For example:
git tag v0.1.1
Running the Script
I wrote this script in Python, but it has no external dependencies—it's implemented entirely using the standard library, which is quite convenient.
python scripts/build_docker.py
PS: In the future, I plan to rewrite this script in C# or Go to support AOT (Ahead-of-Time Compilation) and add it as a tool to the system PATH for easier use.
Script
Next, I'll provide a simplified version of the script.
Due to space limitations, I can't include the complete code in this article. If needed, you can reply with "半自动 CICD 脚本" (Semi-automatic CICD Script) on our WeChat official account to get it.
This script, totaling just over a hundred lines, is small but fully functional, implementing complete features.
Initially, I used paramiko.SSHClient to establish SSH connections. However, I later decided it was better not to introduce additional complexity and ultimately simplified it like this, directly using the system's built-in SSH command.
#!/usr/bin/env python3
-- coding: utf-8 --
""" Docker Image Build, Push, and Remote Deployment Script
Features:
- Retrieve the latest git tag as the version number
- Build Docker image and push it to the configured image repository
- SSH connect to the remote server for automatic deployment
Configuration (environment variables or default values):
- REGISTRY_URL: Image repository (镜像仓库) address, e.g.: registry.example.com
- REGISTRY_NAMESPACE: Image repository namespace
- IMAGE_NAME: Image name
- REMOTE_HOST: Remote server configuration, e.g.: user@server-ip -p 2022
- REMOTE_PROJECT_PATH: Remote project path """
import os import sys import subprocess import threading from typing import Optional, Tuple
Default Configuration
DEFAULTS = { 'REGISTRY_URL': 'registry.example.com', 'REGISTRY_NAMESPACE': 'namespace', 'IMAGE_NAME': 'image-name', 'REMOTE_HOST': 'host-name', # Remote server address or Host alias in ~/.ssh/config 'REMOTE_PROJECT_PATH': '/path/to/project', }
def get_config(key: str) -> str: """Get configuration value, prioritizing environment variables, otherwise using default values.""" return os.environ.get(key, DEFAULTS.get(key, ''))
def _reader_thread(pipe, lines_list, stream_to_print_to): """Read pipe output in a separate thread.""" try: for line in iter(pipe.readline, ''): lines_list.append(line) if stream_to_print_to: # Real-time printing stream_to_print_to.write(line) stream_to_print_to.flush() finally: pipe.close()
def run_cmd(cmd: str, show_output: bool = True) -> Tuple[int, str, str]: """ Execute command and display output in real-time, while capturing the output. Returns exit code, stdout, and stderr. """ if show_output: print(f"Executing: {cmd}")
process = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
stdout_lines = []
stderr_lines = []
stdout_thread = threading.Thread(
target=_reader_thread,
args=(process.stdout, stdout_lines, sys.stdout if show_output else None)
)
stderr_thread = threading.Thread(
target=_reader_thread,
args=(process.stderr, stderr_lines, sys.stderr if show_output else None)
)
stdout_thread.start()
stderr_thread.start()
stdout_thread.join()
stderr_thread.join()
returncode = process.wait()
stdout = ''.join(stdout_lines)
stderr = ''.join(stderr_lines)
if returncode != 0:
print(f"\nError: Command execution failed (Return code: {returncode})")
# Error output has already been printed in real-time, no need to repeat here
sys.exit(1)
return returncode, stdout, stderr
def get_latest_tag() -> str: """Get the latest git tag""" _, tag, _ = run_cmd("git describe --tags --abbrev=0") tag = tag.strip() if not tag: print("Error: No git tags found") sys.exit(1) print(f"Latest tag: {tag}") return tag
def deploy_to_remote(version: str) -> None: """Deploy to a remote server.""" host = get_config('REMOTE_HOST') remote_path = get_config('REMOTE_PROJECT_PATH')
print(f"\n🔗 Connecting to {host} via SSH for deployment...")
# 1. Update remote .env file
print(f"\n🔄 Updating remote .env file...")
update_cmd = f'ssh {host} "sed -i \'s/^TAG=.*/TAG={version}/\' {remote_path}/.env"'
run_cmd(update_cmd)
# 2. Restart remote containers
print(f"\n🔄 Restarting remote containers...")
restart_cmd = f'ssh {host} "cd {remote_path} && docker compose up -d"'
run_cmd(restart_cmd)
print("\n✅ Remote deployment complete!")
def main(): print("🚀 Starting Docker image build, push, and deployment process\n")
# 1. Get the latest tag
version = get_latest_tag()
# 2. Build the image
print("\n📦 Building Docker image...")
run_cmd("docker compose build app")
# 3. Tag the image
registry = get_config('REGISTRY_URL')
namespace = get_config('REGISTRY_NAMESPACE')
image_name = get_config('IMAGE_NAME')
registry_image = f"{registry}/{namespace}/{image_name}:{version}"
print(f"\n🏷️ Tagging the image...")
run_cmd(f"docker tag {image_name} {registry_image}")
# 4. Push the image
print(f"\n📤 Pushing image to registry...")
run_cmd(f"docker push {registry_image}")
print(f"Image pushed: {registry_image}")
# 5. Remote deployment
deploy_to_remote(version)
print("\n🎉 All tasks completed!")
if name == "main": main()
## Summary
This really frees up productivity. This solution greatly reduces the workload of deployment.
This method is worth promoting. I've decided to build this script into the [DjangoStarter framework](https://github.com/Deali-Axy/DjangoStarter)~
_EOF_
Greatly improve project deployment productivity! Share a semi-automated CICD implementation solution!
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
部署MatterMost-开源团队协作平台
MatterMost,开源协作平台,自托管安全可控?部署折腾Docker配置,SSO集成文档坑爹。手机App不支持Play Store,界面劝退,何必自虐?SaaS更省心。
2024-08-11 · DevOps · 约 1 分钟
Gitlab的CI_CD自动部署前端项目
前言 之前陆续写了 Gitlab 的安装使用还有 Gitlab CI/CD 的配置使用,已经把 AspNetCore 的后端项目都做了持续集成了,尝到甜头之后,现在前端的项目也要加入自动化部署,所以经过一番折腾,有了本文~ 前文回顾: GitLab 的安装、配置、使用 持续集成指南:GitLab 的 CI/CD 工具配置与使用 跟之前的 AspNetCore 项目操作差不多,我依然使用 docke
2022-09-30 · DevOps · 约 1 分钟
关于DevOps
前天写了Gitlab安装配置的文章,把之前在Gitee企业版上的大部分项目迁移到本地Gitlab管理,用着还不错~ 随着项目的规模逐渐变大以及手头上开发维护的项目逐渐变多,DevOps的重要性也逐渐凸显,目前我们开发、测试和部署的流程还是相对原始,意识到这点之后我开了一个新的博客分类(DevOps),边学边记录~ https://zh.m.wikipedia.org/zh-sg/DevOps
2022-07-21 · DevOps · 约 1 分钟
GitLab的CI_CD配置与使用
前言 写代码这项工作,本质就是将工作自动化,减少手工操作提供效率,~~因为人的本质都是懒狗~~,程序员也不能例外,为了各种意义的效率提升~~(懒)~~,我们需要持续集成工具,将代码测试、编译、发布这些重复性很高的工作,全部变成自动化,减轻工作量,提高效率,解放生产力,~~为华夏伟大复兴贡献力量~~ 说回正题,持续集成工具主流的就 Jenkins 、Gitlab CI/CD 这些,因为我们刚好在用
2022-09-30 · DevOps · 约 1 分钟
讨论
共 0 条加载评论中…