项目容器化改造与devops实践

🐳项目容器化改造与devops实践

本文记录了笔者在项目中使用docker和jenkins的实践,涉及的代码均为简单示例,不作为教程参考。

🐋容器化改造

云原生时代,为了提高应用的拓展性和伸缩性、简化部署流程,对传统项目进行容器化改造已经成为了大势所趋。而在容器领域,除了Docker,其实还有Podman、Containerd、LXD等值得考虑的选择,但作为容器领域的先行者,Docker目前已经构建起了相当丰富的生态。在此情况下,我打算在我们的项目中接入Docker。

📷镜像打包

网上关于go应用的docker部署方案有很多种,但大体上可以分为两类:

  1. 在容器内对代码进行编译,然后运行应用。
  2. 在容器外对代码进行编译,仅在容器内运行编译好的可执行文件。

前者在进行镜像打包的时候,需要以包含go运行时的镜像为基础,进行构建,打包出来的镜像相对会比较大。而后者在进行镜像打包的时候,只需要选择一个尽可能轻量的linux镜像即可,比如alpine。

以下是一份简单的dockerfile示例,beta是我打包好的可执行文件的名称。

bash 复制代码
FROM alpine:latest
ENV TZ Asia/Shanghai
WORKDIR /app
COPY ./config .
COPY ./log .
RUN chmod +x /app/beta
VOLUME ["/app/config/","/app/log/"]
EXPOSE 8080
CMD ["/app/beta"]

在这个环节中有一个需要注意的点:确定好应用在运行时所依赖的目录结构与相关文件,比如此处的config和log目录,在镜像打包阶段,需要将这些目录与相关文件一并复制到镜像中。

然后,使用docker build指令进行进行镜像的构建。

erlang 复制代码
docker build -t myapp1:v1 .

🎹容器编排

在评估了项目目前的业务模块数量和应用发布需求后,我发现我们暂时还用不上K8S等高级的容器编排工具。为了保证应用能够简单且高效地进行发布,降低维护成本,我们决定使用docker-compose。

以下是一份简单的docker-compose.yml示例。

yaml 复制代码
version: '3'
services:
  # 数据库
  mariadb:
    image: circleci/mariadb
    container_name: mariadb
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MARIADB_ROOT_PASSWORD: 456123
      MYSQL_ROOT_HOST: '%'
      MYSQL_USER: test
      MYSQL_PASSWORD: 456123
      TIME_ZONE: Asia/Shanghai
    privileged: true
    volumes:
      - ./db/data:/var/lib/mysql
      - ./db/log:/var/log/mysql
      - ./db/conf:/etc/mysql
      - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime:ro # 时区配置
    restart: always
    networks:
      - my-net
    ports:
      - 3306:3306
​
  # 项目的各个应用模块
  myapp1:
    image: myapp1:v1
    container_name: myapp1
    restart: always
    ports:
      - "8000:8000"
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - /usr/share/zoneinfo:/usr/share/zoneinfo
    extra_hosts:
      - "host.docker.internal:host-gateway"
    depends_on:
      - mariadb
    networks:
      - my-net
​
  myapp2:
    image: myapp2:v1
    container_name: myapp2
    restart: always
    ports:
      - "9000:9000"
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - /usr/share/zoneinfo:/usr/share/zoneinfo
    extra_hosts:
      - "host.docker.internal:host-gateway"
    depends_on:
      - mariadb
    networks:
      - my-net
​
networks:
  my-net:
    driver: bridge
​

这里我们需要注意几个点:

  1. 根据实际的场景需要,选择好容器间的网络连接类型。这里我们选择了桥接网络,并配置了extra_hosts确保容器内部可以正常地与宿主机进行连通。
  2. 通过挂载宿主机的/usr/share/zoneinfo目录,确保go应用在alpine镜像中运行时,不会出现时区问题。具体可参考这篇文章: 容器化Go应用--基础镜像的未知时区问题 (zhihu.com)
  3. 使用environment和command指令,在运行容器的时候,进行一些账户初始化、配置初始化等操作。
  4. 在进行目录挂载的时候,要根据dockerfile中通过volume指令定义好的路径进行配置,避免因挂载路径不存在导致的容器启动错误。

💾存储改造

随着项目接入容器,伴随而来的一个问题是:由于各模块已经通过容器进行了隔离,原来将文件上传到指定文件夹的存储方案已经失效。在此情况下,我参考了一些go相关的文件存储方案,MINIO、seaweedfs、caddy,发现比较符合业务需求且配备了官方sdk的就只有MINIO。它是一个高性能的分布式对象存储解决方案,而且自带了文件的版本管理功能。

🚴devops

至此,我们便可以通过 docker-compose up -d 指令轻松地部署起我们的应用以及项目所依赖的数据库、中间件。

对于上线部署来说,做到这里已经是蛮不错的了。但考虑到我们日常开发过程中,需要快速迭代,进行效果展示,仅仅接入容器,我感觉仍然没办法很好地提高我们的开发效率。于是我打算更进一步,接入devops工具,打通开发到部署的"最后一公里"。

在工具选择中,选择了比较成熟的jenkins,但由于它是使用java编写的,不管是裸机安装还是通过容器进行部署,都需要搭配JDK,比较占用内存资源。

📧配置代码仓库的webhook

通过在代码仓库中设置webhook,即可实现,每当仓库中出现代码更新,就会提醒jenkins进行应用构建。不过在实际生产过程中,正式环境需要确保应用的稳定性,且需要对应用进行版本管理,所以仅仅建议在测试环境中接入webhook功能。

🚝编写shell脚本

以下是一个简单的在jenkins中的shell示例。

bash 复制代码
# 切换到指定路径
cd /home/myapp
​
#清除本地改动
git checkout .
# 拉取最新代码
git pull origin master
​
# 配置go参数并编译
export GO111MODULE=on
# 配置go代理
export GOPROXY=https://goproxy.cn
go env -w GOOS=linux
go build -o beta .
​
# 停止并删除旧容器
docker stop myapp1 && docker rm myapp1
​
# 删除旧镜像
docker rmi myapp1:v1
​
# 构建新镜像
docker build -t myapp1:v1 .
​
# 通过docker-compose启动新容器
docker-compose up -d

在这里,我们通过shell脚本来控制容器的构建。

至此,我们实现了docker+jenkins的部署方案。

💡关于容器的思考

其实刚开始的时候,团队关于接入容器始终是保持一个试探性的态度。为什么呢?就拿最简单的数据库来说,比如我运行一个mysql,如果在运行期间出了问题,我们没有办法立刻定位到相关的文件路径去进行错误排查或修复。虽然我们可以进行目录挂载,但大多的时候还是只能"docker exec"进入容器内,然后再进行排查(且容器只有在正常运行的状态下才能进入,否则只能"docker inspect"去定位容器目录在宿主机中的具体位置)。所以对于运维人员来说,相比传统的部署方式,使用容器只能说在某些方面是方便的,但又会在另一些方面带来不必要的麻烦。

但就像文章开头所说的,容器化始终是大势所趋,谈到云原生,基本也离不开容器。作为开发者,我认为我们还是需要拥抱容器,在实践中找到比较适合自己项目的部署流程。另外,对于个人开发者而言,我始终相信,容器是学习各类新工具的不二选择。

相关推荐
豌豆花下猫17 分钟前
REST API 已经 25 岁了:它是如何形成的,将来可能会怎样?
后端·python·ai
喔喔咿哈哈29 分钟前
【手撕 Spring】 -- Bean 的创建以及获取
java·后端·spring·面试·开源·github
夏微凉.41 分钟前
【JavaEE进阶】Spring AOP 原理
java·spring boot·后端·spring·java-ee·maven
不会编程的懒洋洋2 小时前
Spring Cloud Eureka 服务注册与发现
java·笔记·后端·学习·spring·spring cloud·eureka
NiNg_1_2343 小时前
SpringSecurity入门
后端·spring·springboot·springsecurity
Lucifer三思而后行3 小时前
YashanDB YAC 入门指南与技术详解
数据库·后端
王二端茶倒水4 小时前
大龄程序员兼职跑外卖第五周之亲身感悟
前端·后端·程序员
夜色呦5 小时前
现代电商解决方案:Spring Boot框架实践
数据库·spring boot·后端
爱敲代码的小冰5 小时前
spring boot 请求
java·spring boot·后端
java小吕布7 小时前
Java中的排序算法:探索与比较
java·后端·算法·排序算法