1. DOCKER安装

1.1 前置环境

首先,如果使用CentOS,你至少需要7.4以上,如果使用K8S,则需要7.7以上。从内核角度来说,建议使用8.2及以上。

如果是7.4以下的版本,可以通过设置仓库升级到7.8 或7.9版本。

yum install centos-release kernel
#实际上安装了kernel就可以了,这步可以不做
yum upgrade 

此外,还至少需要以下依赖:

yum install -y yum-utils device-mapper-persistent-data lvm2

1.2.docker-ce安装

由于一些众所周知的原因,不能用docker EE,一般建议社区版docker CE

#默认的海外地址,建议用aliyun的加速地址
#yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install docker-ce -y

1.3. 配置docker

创建或修改此文件 /etc/docker/daemon.json

{
  "registry-mirrors": ["https://xxx.mirror.aliyuncs.com"], //阿里云申请账户时候给的加速地址
  "insecure-registries": ["10.35.9.161"], //可能的私有仓库
  "log-opts": {"max-size":"64m", "max-file":"3"}
}

部分环境存在/etc/systemd/system/docker.service,可能存在–insecure-registries,会发生冲突,删除字段即可。

配置完后即可启动docker

service docker start
docker login registry.cn-hangzhou.aliyuncs.com -u your_account -p your_passwd

1.4 (可选)配置代理

某些情况下,内网机器需要连接公网服务器,如果是CentOS8以上(7不行),可如下操作:

mkdir -p /etc/systemd/system/docker.service.d
vi /etc/systemd/system/docker.service.d/http-proxy.conf
systemctl daemon-reload
service docker restart

文件内容如下:(假设HTTP代理为192.168.1.100:8080)

[Service]
Environment="HTTP_PROXY=http://192.168.1.100:8080"
Environment="HTTPS_PROXY=http://192.168.1.100:8080"
Environment="NO_PROXY=localhost,127.0.0.1"

2. 生成DOCKER

2.1 简易镜像生成法

首先需要准备一份Dockerfile

FROM hub.docker.com/centos/centos:7.5.1804
COPY myapp/ /opt/myapp/
CMD ["/opt/myapp/start.sh"]

以上述为例,FROM表示基础镜像。
COPY为需要增加文件到DOCKER中的具体位置
CMD为默认命令行

然后就可以生成了

docker build -t hub.my.com/mycomp/myapp:1.0 .

生成完毕后,我们可以上传到仓库当中,注意tag时的version,可以和我们build时不一致;push时需要和tag一致,表示最终上传这个tag。

TAG=`docker images | grep myapp | grep 1.0 | awk {'print $3'} | head -n 1`
docker tag $TAG hub.my.com/mycomp/myapp:1.0
docker push hub.my.com/mycomp/myapp:1.0

2.2 高阶镜像玩法

实际生产过程中,我们可能需要基于标准镜像,准备两份版本。一份包含了完整的编译环境,一份为生产环境运行使用。这样我们就可以在任意部署了docker 的机器上进行三方编译工作,不再依赖编译的宿主机。
以下面的Dockerfile为例,直接用编译镜像生成文件,并直接在生产镜像中应用,制作成运行镜像。

FROM hub.my.com/mycomp/centos78_devel:1.0 AS BUILDS
COPY . /opt/code/mysrc
RUN cd /opt/code/mysrc && ./build.sh

FROM hub.my.com/mycomp/centos78_release:1.0
COPY --from=BUILDS /opt/code/mysrc/dist /opt/myapp
CMD ["/opt/myapp/bin/start_in_docker.sh"]

为了加速源码目录的COPY动作,可以在需要的目录下,设置文件.dockerignore

.svn
**/*.d

2.3 容器的跨仓库迁移

由于实际使用中,生产系统的仓库和开发环境的仓库在物理上并不打通,需要导出成文件方便迁移,常见的有docker save和docker export。关于两者的区别,由详解可知,save不破坏层,更能保持镜像的原汁原味,方便复用,且在基础容器较大的情况下,导出多个容器时反而更节约空间。

开发机上:

docker save hub.my.com/mycomp/myapp:1.0 hub.my.com/mycomp/helloworld:1.0 > 1.tar

生产环境下:

 docker load < 1.tar

3. 调试DOCKER

以上命令表示以HOST模式(直接拥有真机地址)运行镜像。网络模式这里就不展开讨论了。

docker run -it --net=host hub.my.com/mycomp/myapp:1.0
#运行后,可以用ps查看当前正在运行的容器,并通过exec进入该容器
docker ps
docker exec -it 00eeaa6467cc /bin/bash

以下也是一种利用编译镜像编译的办法。
即通过-v参数将本地磁盘的当前路径,映射到docker内的/opt/code目录,并通过-w参数指定该目录为运行目录。
通过-e参数将版本号作为环境变量REVISION,实现自动标记版本号。
和上面的方法的区别在于中间产物会遗留在真实硬盘上,减少复制的成本,方便试错

docker run --rm -it -v $(pwd):/opt/code/ -w /opt/code/ -e REVISION=${REVISION} hub.hitry.io/hitry/centos78_devel:1.0 ./build.sh

4. yaml的一些编写技巧

由于我们是使用了Rancher+K8S的方案,因此对于从机来说,直接按命令运行就行了,K8S的部署就不展开了。
本段开始,都是基于K8S/Rancher来说的。

4.1 活用环境变量的fieldRef

对应在Rancher中就是Field,目前只有这些信息可以获取,复杂的就需要K8SAPI了

名称含义
metadata.namePOD名称,有状态服务时很有用
metadata.namespace服务命名空间
metadata.uidUID
spec.nodeName宿主机HostName
spec.serviceAccountNameserviceAccount名
status.hostIP宿主机IP
status.podIPPOD IP
status.podIPsPOD IP列表

4.2 活用亲和性和反亲和性

4.2.1 利用亲和性设置启动节点

我们可以为每个物理节点打上标签,然后利用亲和性,我们可以指定期望在哪台机器上运行服务。甚至可以结合DaemonSet保证所有的期望节点都跑一遍(虽然不推荐这么干)。

例如:
我们期望最好(可以不是)这个服务在main_node上运行,必须在gpu_node上运行,且不允许在inner_node上运行。如果使用rancher,这个有界面可以配。

spec:
  template:
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - preference:
              matchExpressions:
              - key: main_node
                operator: In
                values:
                - "true"
            weight: 100
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: gpu_node
                operator: In
                values:
                - "true"
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: inner_node
                operator: NotIn
                values:
                - "true"

4.2.2 利用反亲和性实现应用互斥或最多每机一实例

在rancher中并不提供反亲和性的图形化配置,不过可以手工修改yaml支持此K8S的特性
比较典型的一个应用场景是我希望某个服务(特别是网络类型为HOST模式的)最多每台机器起一份,避免端口冲突,或GPU使用冲突。

以流媒体服务为例,其存在大范围UDP端口段监听,必然使用HOST模式。我也不需要使用DaemonSet方式每机起一份加大问题定位的复杂度,可能只需要双机热备,或按需扩容。因此,正常使用时,只要保证运行的实例数量小于节点数量即可;但是当部分节点瘫痪,使得实例数大于节点数时,就需要阻塞那些多出来的实例,把他们挂起,不要拥挤到同一台机器跑起来。

此时可以这样处理:

  1. 为该服务分配一个标签,key为app,value为mediasvr
spec:
  template:
    metadata:
      labels:
        app: mediasvr
  1. 设置反亲和性,不允许在已经有app=mediasvr标签的机器上运行(不同的服务都设置key=app,是并行存在的,如果你想A服务和B服务互斥,也可以用相同的办法)
spec:
  template:
    spec:
      affinity:
		podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - mediasvr
            topologyKey: kubernetes.io/hostname

5. K8SAPI

在某些情况下,我们需要更为复杂的操作,可以使用K8S的API进行复杂查询

K8S API v1.20

调用K8S的API需要的token,基本有两种方法:

  1. 使用你本身的serviceaccount进行查询。但是需要注意的是默认的default账户权限很低,很多操作需要自定义账户并提权。
  2. 可以创建一个公用的API Key,并将Bearer Token保存下来(比方说放置到config map中,让具体的服务通过环境变量引用得知)。在rancher中,操作流程为:
    在这里插入图片描述
    “右上角下拉”-> “API & Keys” -> “添加Key”-> “选择作用范围到指定集群”
    *注意:不选择作用范围,这个key只能调用rancher的API,选择了集群,则会自动在K8S中创建账户

5.1 利用K8S API 获取所有节点的IP列表

举个实际的例子,当一个HOST类型的服务,启动了某个内部通讯用的监听,我并不想让外部来连接,需要一个IP白名单。在传统的方案中,只能依靠部署人员手工配置,或者直接网络隔离(防火墙/NAT等)。
利用K8S的API,我们可以轻易的得知所有服务节点清单,以配置白名单。

curl -H "Authorization: Bearer $token" -X GET -H 'Accept: application/json' -H 'Content-Type: application/json' --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://127.0.0.1:6443/api/v1/nodes > nodes.json

需要注意的事项:
1)$token就是上面说的Bearer Token
2)如果不是HOST模式,那么请求的地址就不能是127.0.0.1,而是上面提到的status.hostIP中获取到的宿主机IP。
3)CA证书可以不指定,设置成允许非安全连接也能用。

此时拿到的是一个很长的json。我们可以使用一个简易的Python或者其他语言来解析他。

python fetch_ip.py nodes.json > whitelist.conf
#!/usrbin/env python

import json
import string
import httplib,sys

if __name__ == '__main__' :
    if len( sys.argv ) < 2 :
        print "Please run like this: python fetch_ip.py <filePath>"

    f = open(sys.argv[1],'r');
    rootnode = json.load(f)
    for item in rootnode["items"]:
        for addr in item["status"]["addresses"]:
            if addr["type"] == "InternalIP":
                print addr["address"]

5.2 利用K8S API 获取指定服务的POD列表

还是以之前的mediasvr为例。当某些场景下,我希望获取所有的mediasvr清单。
可以利用设置标签+labelSelector进行筛选。本质上来说你找的是拥有这个标签的POD,不是这个服务名的。

curl -H "Authorization: Bearer $token" -X GET -H 'Accept: application/json' -H 'Content-Type: application/json' --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://127.0.0.1:6443/api/v1/namespaces/default/pods?labelSelector=app%3Dmediasvr

6. coredump调试

容器内的coredump默认会被宿主机截取,可以在宿主机上用coredumpctl命令获取到

coredumpctl list -r

TIME                            PID   UID   GID SIG COREFILE  EXE
Wed 2023-09-27 02:19:13 GMT  2418264     0     0   6 present   /opt/stdgw/StdsipGateway

可见有个PID为2418264的coredump
接着我们可以走到容器内应用所在的实际目录

#根据容器内应用定位到实际的目录
cd /proc/123456/cwd
coredumpctl dump 2418264 --output 2.core

导出后的coredump,就可以在容器内进行gdb调试了。
*注意,由于宿主机的libc之类的库和容器内很可能不一样,不能用宿主机的gdb进行调试

7.以普通用户启动docker

直接用非root用户启动docker,可能会收到以下错误

$ docker images
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/images/json": dial unix /var/run/docker.sock: connect: permission denied

有时候还会有这个看起来很奇怪的错误

docker run -it -u root \
> -e ASCEND_VISIBLE_DEVICES=0 \
> --device=/dev/davinci0 \
> --device=/dev/davinci_manager \
> --device=/dev/devmm_svm \
> --device=/dev/hisi_hdc \
> -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
> -v /usr/local/dcmi:/usr/local/dcmi \
> -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
> ascendhub.huawei.com/public-ascendhub/ascend-infer:23.0.0-ubuntu18.04 \
> /bin/bash
docker: unknown server OS: .

都是因为该用户没有权限导致的。正确的做法是将当前用户(例子中是HwHiAiUser)加入docker组。

$ gpasswd -a HwHiAiUser docker 
$ service docker restart

执行完毕后需要新开会话窗口才能生效。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐