简易DOCKER/K8S使用心得
1. 生成DOCKER首先需要准备一份DockerfileFROM hub.docker.com/centos/centos:7.5.1804COPY myapp/ /opt/myapp/CMD ["/opt/myapp/start.sh"]以上述为例,FROM表示基础镜像。COPY为需要增加文件到DOCKER中的具体位置CMD为默认命令行然后就可以生成了docker build -t hub.m
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.name | POD名称,有状态服务时很有用 |
metadata.namespace | 服务命名空间 |
metadata.uid | UID |
spec.nodeName | 宿主机HostName |
spec.serviceAccountName | serviceAccount名 |
status.hostIP | 宿主机IP |
status.podIP | POD IP |
status.podIPs | POD 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方式每机起一份加大问题定位的复杂度,可能只需要双机热备,或按需扩容。因此,正常使用时,只要保证运行的实例数量小于节点数量即可;但是当部分节点瘫痪,使得实例数大于节点数时,就需要阻塞那些多出来的实例,把他们挂起,不要拥挤到同一台机器跑起来。
此时可以这样处理:
- 为该服务分配一个标签,key为app,value为mediasvr
spec:
template:
metadata:
labels:
app: mediasvr
- 设置反亲和性,不允许在已经有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需要的token,基本有两种方法:
- 使用你本身的serviceaccount进行查询。但是需要注意的是默认的default账户权限很低,很多操作需要自定义账户并提权。
- 可以创建一个公用的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
执行完毕后需要新开会话窗口才能生效。
更多推荐
所有评论(0)