项目容器化改造与devops实践

2023年 7月 25日 52.6k 0

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

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

🐋容器化改造

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

📷镜像打包

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

  • 在容器内对代码进行编译,然后运行应用。
  • 在容器外对代码进行编译,仅在容器内运行编译好的可执行文件。
  • 前者在进行镜像打包的时候,需要以包含go运行时的镜像为基础,进行构建,打包出来的镜像相对会比较大。而后者在进行镜像打包的时候,只需要选择一个尽可能轻量的linux镜像即可,比如alpine。

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

    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指令进行进行镜像的构建。

    docker build -t myapp1:v1 .
    

    🎹容器编排

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

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

    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
    ​
    

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

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

    image-20230722133415513.png

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

    🚴devops

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

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

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

    📧配置代码仓库的webhook

    image-20230722131412331.png

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

    🚝编写shell脚本

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

    # 切换到指定路径
    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脚本来控制容器的构建。

    image-20230722132313808.png

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

    💡关于容器的思考

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

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

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论