摸鱼心法第一章——和配置文件说拜拜

开发 前端
从本地开发和容器运行两个角度来看,本地开发的时候读取配文件比读取环境变量方便,容器运行中读取环境变量比读取配置文件方便,我想说你俩搁这卡bug呢?

为了能摸鱼我们团队做了容器化,但是带来的问题是服务配置文件很麻烦,然后大家在群里进行了“亲切友好”的沟通

图片图片

图片图片

图片图片

图片图片

对比就对比,简单对比下独立配置中心和k8s作为配置中心的区别


独立配置中心

k8s作为配置中心

学习成本

1.运维要学习搭建、维护

2.研发和研发都需要学习配置中心的工具、系统如何使用

1.熟悉yaml/json语法即

2.研发只需要解析环境变量,无需关注注入细节

适配工作量

  1. 代码需要引入对应配置中心的库进行开发
  2. 需要在对应的配置中心管理

  1. 服务直接解析环境变量即可
  2. k8s原生支持环境变量,并且支持通过configmap,secret资源注入到服务的环境变量中

集群维护成本

额外维护成本

保证集群etcd稳定即可,无额外成本

服务发现

支持服务发现

支持服务发现

云资源费用

增加成本

无额外成本

对比结果出来后,群里的研发也觉得k8s作为配置中心不错了

图片图片

图片图片

我这里想问问在看文章的同学:是不是都觉得运维的东西很简单?还有是不是个锅都甩给运维?像这样的研发你身边多么还是说你也是这样的研发? 

继续今天的话题,既然服务要在k8s里运行,同时也要把k8s作为配置中心使用,那服务适配需要做些啥? 咱们先列一个清单

图片图片

服务要优雅的适配容器化环境,需要解决以下问题

  • 避免繁琐的定义和解析服务环境变量
  • 服务在本地调试下和容器环境运行两种场景下,对于环境变量的解析需要无缝切换
  • 服务的dockerfile可根据服务信息自动生成,尽量避免人工操作

1.首先说环境变量的问题

从本地开发和容器运行两个角度来看,本地开发的时候读取配文件比读取环境变量方便,容器运行中读取环境变量比读取配置文件方便,我想说你俩搁这卡bug呢?

但是这个问题其实不难,解决逻辑也很简单。那就是采用覆写的思路,如果环境变量里读取到了值就用环境变量的,否则就用代码里的值。

那按照这个思路是得有一个配置文件,然后服务读取这个配置文件?可惜这个和我们团队的一个追求相违背——代码及文档

说到文档,插个题外话,对于写文档这事儿。。。

看别人的东西,你TM文档呢? 

做自己的东西,这TM还用写文档?

回到环境变量这个问题来,其实在代码里面定义变量并提供覆写的能力就足够了。考虑到在本地开发调试的时候,需要频繁修改变量的值,无论是修改代码里的变量值或者修改环境变量还是稍显麻烦,所以覆写的信息可以来源于环境变量或一个覆写的变量文件

流程如下

图片图片

针对提供反射机制的编程语言结合一定的规则,环境变量的key可以直接从定义的结构体里获取无需额外维护。无反射类型的编程语言也可以按照这个思路实现,只是稍显麻烦。这样环境变量的问题解决了,然后就是dockerfile的问题

2.dockerfile如何自动生成

我们再看看刚才列的清单

图片图片

首先说说核心问题如何编译,有两种方式

1.直接在dockerfile里面写编译过程

直接手写dockerfile没有问题,因为服务的开发人员最清楚自己的服务需要怎么编译,但是不同的服务总会出现差异化的编译过程,这样从代码自动生成dockerfile的角度来讲不可控

2.makefile文件

通过makefile来执行编译步骤就解决了差异化的问题,在dockerfile里只需要执行类似make build的固定命令便完成了服务编译全过程。自动化工具按照固定的dockerfile模板生成文件,makefile完成具体的编译过程,这样服务编译与工具完美解耦

核心问题解决了,至于dockerfile如何生成,每种编程语言都可以采用自身语言提供的模板库进行生成dockerfile了。即便不用模板,拼接字符串也是可以的,条条大路通罗马。

然后我们再谈谈为什么会有编译镜像和运行镜像的区别,我们看看下面这个流程

图片图片

从流程可以看出,服务在k8s里面启动时会从镜像仓库拉去服务镜像,这里存在一个网络传输的问题,内网都不说了,如果从公网拉镜像,并发量高一点,拉的再频繁点,不管是固定带宽还是按流量计费,老话说得好,这不就是小刀剌了貔貅腚——拉的都是钱

所以我们期望的是镜像足够小,这样在部署服务的时候更快更省钱,尤其是首次部署的时候(这里涉及到docker 分层的问题不做展开)。编译镜像一般都非常庞大并不适合作为运行镜像使用,只需要提供编译环境,编译完成后将编译后的文件放入一个很小的运行镜像中即可

以我们团队采用的是Golang语言为例,编译镜像目前采用的1.20.5-buster,AMD64的镜像大小为275M,ARM的镜像大小为264M,运行镜像采用的是gcr.io/distroless/static-debian11,最终运行镜像大小在20M左右,这不得起飞了啊,拉镜像就跟玩儿一样。

好了,今天的文章主要分享了在容器化环境下,通过抛弃服务配置文件而采用环境变量的形式来解决配置注入的问题,自动生成dockerfile需要避免的坑,希望对在走容器化道路的同学有所帮助。如果大家想听听其他的可以留言或者私信我们。

接下来就是Golang的福利时间,我们将这个变量注入的库进行了开源。现在用gin框架写个demo来演示环境变量注入和生成dockerfile。(我们在gin框架上加了一点点东西,这样更好用)

首先创建一个工程目录

图片图片

global/config.go这个文件长这样

package global


import (
  "github.com/kunlun-qilian/conflogger"
  "github.com/kunlun-qilian/confserver"
  "github.com/kunlun-qilian/confx"
)


func init() {
  confx.SetConfX("demo-docker", "..")
  confx.ConfP(&Config)
}


var Config = struct {
  Logger  *conflogger.Log
  Server  *confserver.Server
  TestEnv string `env:""`# 环境变量标记,只要有这个标记则支持注入
}{
  Server: &confserver.Server{
    Port: 80,
    Mode: "debug",
  },
  TestEnv: "123",
}
github.com/kunlun-qilian/confx
这个库的作用就是注入环境变量和生成dockerfile,
单独出来了一个库,只要是这个工程目录结构都可以使用

运行之后会生成config/default.yml,这个环境变量文件就是每次启动服务后根据上述global/config.go文件自动生成的默认配置文件,这个文件是作为后续本地覆写配置文件的蓝本,免得不知道环境变量是啥,环境变量规则是“服务名__环境变量名”

DEMO_DOCKER__Logger_Level: ""
DEMO_DOCKER__Logger_Output: Always
DEMO_DOCKER__Server_Mode: debug
DEMO_DOCKER__Server_UseH2C: "false"
DEMO_DOCKER__TestEnv: "123"

demo里加入了一个接口来返回TestEnv的值

图片图片

在本地开发的时候需要覆写默认值的时候,只需要在config目录下加入一个叫做 local.yml(这个放gitignore里)的文件并添加想替换的值

图片图片

重新运行一下服务,再看接口,变量被local.yml里面的值替换了

图片图片

然后我们再通过当前命令行会话中注入一个环境变量,然后启动

export DEMO_DOCKER__TestEnv=terminal_789 && go run main.go

值又被替换成了环境变量的值,有了这个还要啥自行车?

图片图片


再看看生成的dockerfile,下面这个就是自动生成的默认dockerfile

FROM dockerproxy.com/library/golang:1.20-buster AS build-env


FROM build-env AS builder




WORKDIR /go/src
COPY ./ ./


# build
RUN make build WORKSPACE=demo-docker


# runtime
FROM gcr.dockerproxy.com/distroless/static-debian11


COPY --from=builder /go/src/cmd/demo-docker/demo-docker /go/bin/demo-docker




EXPOSE 80


ARG PROJECT_NAME
ARG PROJECT_VERSION
ENV PROJECT_NAME=${PROJECT_NAME} PROJECT_VERSION=${PROJECT_VERSION}


WORKDIR /go/bin
ENTRYPOINT ["/go/bin/demo-docker"]

上文中提到的几个配置信息,咱们定义了一个结构,包含了编译镜像,运行镜像,GOPROXY代理,openapi文件,奥,差点忘了这篇不涉及到openapi,篇幅有限这个在后续篇章里讲,你们懂的

type DockerConfig struct {
  BuildImage   string
  RuntimeImage string
  GoProxy      GoProxyConfig
  Openapi      bool
}


type GoProxyConfig struct {
  ProxyOn bool
  Host    string
}

在global/config.go中的init方法中,留了入口

func init() {
  confx.SetConfX("demo-docker", "..", confx.DockerConfig{
    BuildImage:   "private-harbor.xxx.com/xxx/builder:v1.0.0",
    RuntimeImage: "private-harbor.xxx.com/xxx/runtime:v1.0.0",
    GoProxy: confx.GoProxyConfig{
      ProxyOn: true,
      Host:    "https://goproxy.cn,direct",
    },
  })
  confx.ConfP(&Config)
}

然后我们再重新运行一下看看结果,编译镜像、运行镜像、代理都更新了

FROM private-harbor.xxx.com/xxx/builder:v1.0.0 AS build-env


FROM build-env AS builder




ARG GOPROXY=https://goproxy.cn,direct


WORKDIR /go/src
COPY ./ ./


# build
RUN make build WORKSPACE=demo-docker


# runtime
FROM private-harbor.xxx.com/xxx/runtime:v1.0.0


COPY --from=builder /go/src/cmd/demo-docker/demo-docker /go/bin/demo-docker




EXPOSE 80


ARG PROJECT_NAME
ARG PROJECT_VERSION
ENV PROJECT_NAME=${PROJECT_NAME} PROJECT_VERSION=${PROJECT_VERSION}


WORKDIR /go/bin
ENTRYPOINT ["/go/bin/demo-docker"]

如果服务有多个端口怎么处理?还是从global/config.go中下手,增加一个 TestPort 的变量,tag中加上 `env:"opt,expose"`

var Config = struct {
  Logger  *conflogger.Log
  Server  *confserver.Server
  TestEnv string `env:""`


  TestPort int `env:"opt,expose"` # 看这里,看这里
}{
  Server: &confserver.Server{
    Port: 80,
    Mode: "debug",
  },
  TestEnv:  "123",
  TestPort: 9090,
}

然后咱们再运行一次,9090端口暴露出来了,这带手表了,带啥手表了?

FROM private-harbor.xxx.com/xxx/builder:v1.0.0 AS build-env


FROM build-env AS builder




ARG GOPROXY=https://goproxy.cn,direct


WORKDIR /go/src
COPY ./ ./


# build
RUN make build WORKSPACE=demo-docker


# runtime
FROM private-harbor.xxx.com/xxx/runtime:v1.0.0


COPY --from=builder /go/src/cmd/demo-docker/demo-docker /go/bin/demo-docker




EXPOSE 9090


EXPOSE 80


ARG PROJECT_NAME
ARG PROJECT_VERSION
ENV PROJECT_NAME=${PROJECT_NAME} PROJECT_VERSION=${PROJECT_VERSION}


WORKDIR /go/bin
ENTRYPOINT ["/go/bin/demo-docker"]

最后贴上全球最大同。。不对,是github链接,后续我们还会逐步开源一些工具

工具包confx: https://github.com/kunlun-qilian/confx
本文的demo:  https://github.com/kunlun-qilian/gin-demo


责任编辑:武晓燕 来源: 佳华云原生实践
相关推荐

2014-01-13 11:22:28

storm

2015-12-30 09:25:47

编程故事printf

2011-07-19 17:25:14

jQuery MobiAndroid

2012-02-09 10:39:37

AndroidWeb App官方文档

2013-10-15 14:18:31

2023-05-24 16:13:31

ChatGPT神经网络

2011-07-20 10:27:18

jQuery Mobi手机新闻浏览器

2009-02-25 09:20:42

职场调查压力

2023-08-10 09:44:38

YAML文件环境

2010-12-07 10:40:27

软考系统架构设计师

2022-11-11 08:16:51

2017-05-28 12:26:58

5GTCPIP

2010-12-09 10:59:54

三级PC技术

2011-01-04 13:51:44

PC技术

2010-12-28 10:55:07

PC技术

2010-12-13 11:50:48

三级PC技术

2012-10-12 13:35:51

Windows 8

2011-01-21 15:08:45

Sendmail

2012-05-28 13:56:41

Web

2009-06-24 14:17:00

BackingBeanJSF配置文件
点赞
收藏

51CTO技术栈公众号