Eisen's Blog

© 2024. All rights reserved.

国内环境下更好的 docker 镜像获取

2018 June-10

最近欠了好多的 blog,是在是有点忙,周末也被各种事情缠身。今天趁周日的最后半个小时抓紧记录下来一些调研的成果。

国内的网络环境大家都知道,一方面是不稳定的宽带,另一方面就是对一些国外网站访问的不可靠。今天集中记录一下自己在 docker 镜像处理方面的一些小技巧。

加速 docker pull

Docker 本身提供了一个叫做 docker registry mirror 的东西,就是为了减少重复的镜像下载所产生的额外带宽,在国内访问 docker 官方 image 极其缓慢的场景下这种需求尤为凸显。如果不设置相应的 mirror 国内服务器下载镜像真是举步维艰。

通过在 /etc/dockeer/daemon.json 做相应的配置就可以添加一个 registry mirror:

{
  "registry-mirrors": ["<your registry mirror url>"]
}

配置之后需要重启 docker。

$ sudo pkill -SIGHUP dockerd

docker-cn

Docker 中国 估计是 docker 为了在中国开展业务搞的子公司吧?不应该是什么山寨网站吧。其提供了加速国内 docker pull 的镜像地址 https://registry.docker-cn.com。按照上述的配置方式配置即可使用:

{
  "registry-mirrors": ["https://registry.docker-cn.com"]
}

然而很遗憾,这个速度并不理想,以我目前所在的网络环境,下行速度 4MB/s 拉取 ubuntu 镜像的速度大概也就是 400KB/s ~ 500KB/s。

daocloud

daocloud 作为一个做 caas 的公司为国内提供了号称免费的 mirror 构建服务。登录控制台就可以看到如图所示的位置的加速器按钮了,点进去就有相应的脚本了。

注意 虽然人家是好意给了一个 shell 脚本帮助修改 /etc/docker/daemon.json 的配置,然而如果你的 docker 不止有一个 runtime,比如你像我这样需要跑 nvidia docker 的 runtime,那么这个脚本会把你的配置搞砸...建议直接想我这样手动配置:

{
    "registry-mirrors": ["{{ docker_mirror }}"],
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
        }
    }
}

其中将 {{ docker_mirror }} 替换成 daocloud 提供的 mirror 即可。

配置之后重启 dockerd 感受下速度吧,同样的网络环境,基本是 2MB/s ~ 3MB/s。

拉取更难以获取的镜像

上述的 docker-cn 以及 daocloud 仅仅是支持官方 docker.io 镜像的加速,然而要知道当今世界 google 的 google cloud platform 做的是相当不错,google 旗下的 kubernetes 基本是当前 PaaS 的不二之选,其官方镜像域名 gcr.io 下有大量 docker.io 无法取代的重要资源。而这些资源早早的已成为了墙外之物。

做搬运工

为了获取 gcr.io 域名下的镜像,我们可以在境外创建一个 vps 然后通过蚂蚁搬家的方式一点一点挪过来:

  1. 把 gcr.io/image-name:tag pull 到 vps 上 docker pull gcr.io/image-name:tage
  2. 重新打标签到自己 docker.io 的账号下 docker tag gcr.io/image:tag <username>/image:tag
  3. 把新镜像推送到 docker.io docker push <username>/image:tag

当然你可以写一个脚本,把自己用得着的镜像一个个 push 到 docker.io 中。甚至有人会写一些类似于 webhook 的东西,当 gcr.io 一些特定项目的镜像更新后会自动触发相应的流程自动托运新的镜像。不过不论如何这样的坏处显而易见:这是一个体力活,虽然有一些加速的脚本但是我依然需要更新脚本,管理 webhook...我先前已经用这个方法搞了一堆这样的镜像了...都是眼泪...

google 一下发现这种方式在用 kubeadam 安装 kubernetes 的场景被很多人采用了。

docker proxy 配置

既然有了 vps 自然是可以直接搭建一个代理的,docker 本身是支持在 docker pull 使用代理的,那么配个代理不久解决问题了吗。具体怎么配置代理这里就不讲了,我只记录 docker 这边的配置:

首先 mkdir /etc/systemd/system/docker.service.d

然后创建 /etc/systemd/system/docker.service.d/http-proxy.conf,添加内容如下:

[Service]
Environment="HTTP_PROXY=http://user01:[email protected]:8080/"
Environment="HTTPS_PROXY=https://user01:[email protected]:8080/"
Environment="NO_PROXY=localhost,.docker.io,.docker.com,.daocloud.io"

当然要使用自己的 HTTP_PROXYHTTPS_PROXY,然后把不想使用代理的域名添加到 NO_PROXY,尤其是使用的镜像域名和 docker.io 应该考虑在内。

最后更新 systemctl 并重启服务

$ systemctl daemon-reload
$ systemctl restart docker

之后可以用以下镜像测试一下:

$ docker pull k8s.gcr.io/kube-scheduler-amd64:v1.10.2

注意

在查找有关 docker proxy 内容时会发现有两种搜索结果,一种是我上述讲的 docker pull 时采用代理的方法;另一种是如何在 docker container 中配置代理:Configure Docker to use a proxy server 这个迷惑性还是有的...不过第二种情况以后也可能会用得上。


docker 存储

2018 May-25

由于自己接触 docker 的时候和现在的 docker 不少的 api 已经有所变化,还是需要更新一下自己的知识。这篇文章大部分源自 docker 的官方文档,然后还有部分自己添油加醋。

在 docker 中所有的文件是存储在容器的 writable container layer。存在以下的问题:

  • 容器里的数据不好拿出来
  • 在容器里写依赖于 storage driver 采用了 union filesystem 效率低下

然而 docker 提供了可以直接往 host 机器写内容的方式:

  1. volumes
  2. bind mounts
  3. tmpfs

采用这些方式,即使容器关了甚至删除了数据依然不会丢失。

选择适合的数据绑定方式

首先不论是采用哪种方式,在容器里看起来都是一样的。

  • Volume 将数据存储在 host 但是其实是由 docker 管理的 /var/lib/docker/volumes 非 docker 无法使用
  • Bind mounts 是可以将数据随意存储在任意 host system 文件系统中,任何非 docker 应用也有权利对其进行使用
  • tmpfs mounts 将文件存储在内存中

更多详情

Volume

Volume 可以通过 docker volume create 显式创建,当然 docker 也可以在运行的时候创建 volume(比如在 Dockerfile 里面有 VOLUME 语法时如果没有没有显式绑定 volume 那么就会默认创建一个新的 volume)。

docker volume create data
docker run -v data:/data ...

注意 docker volume inspect xxx 会得到如下的结果:

[
    {
        "CreatedAt": "2018-05-22T07:53:58Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/test/_data",
        "Name": "test",
        "Options": {},
        "Scope": "local"
    }
]

其存储的位置为 /var/lib/docker/volumes/test/_data 但是在 Mac OS 下你会发现根本没有这个目录,那是因为 Mac 下的 docker 还是以 linux 虚拟机的形式运行的,你所看到的是其虚拟机下的目录而不是 Mac 下的目录。

Bind mounts

其功能相对 volume 比较有限。但是其好处是可以绑定任意的外部目录给 docker,尤其是在做一些外部数据共享给 docker 的时候非常适合。

docker run -v /tmp/data:/data:ro ...

Tmpfs

我自己是没有直接使用过。基本就是用于沙盒环境吧。

docker run --tmpfs /data ...

注意

两个特殊的场景:

  1. 如果你 mount 一个 empty volume 到一个 container 中已经存在的文件夹,那么 container 会将容器目录中的内容拷贝到这个空的 volume 里面
  2. 如果你使用 bind mount 或者一个非空的 volume 绑定到容器中某一个目录,那么目录里面的东西就会隐藏而只能看到 bind mount 或者 volume 里面的内容。注意,原有的内容不是被删除了而仅仅是被隐藏了

相关文献

  1. Docker Volumes
  2. Use bind mounts
  3. Use Volumes

用 kubeadm 部署 aws 环境下的 kubernetes

2018 April-24

最近一个多月开始折腾在 国内 环境部署 kubernetes 集群。是的,确实这还是一个工作内容,和直接在什么 aws googlecloud 或者是 rancher 2.0 直接点点就能创建有一个集群不一样。之所以还要付诸这样的精力去做这件事有两个原因:

  1. kubernetes 默认部署安装依赖于很多 google 的域名,而因为特殊的国内网络环境,那些自动执行创建集群的工具默认都是不适用的,我不得不把 kubernetes 集群部署搞得一清二楚才能明白具体哪里去 hack 那些被 block 的资源
  2. 我们有自己搭建 bare metal 集群的需求,而那些可以随便点点就能构建集群的方式都是支持公有云的方案

不过因为我刚刚搞定了 aws 环境下 kubernetes 的部署,趁热打铁我就先记录一下这一部分。

aws 环境下 kubernetes 的特殊性

k8s 有一个 cloud-provider 的概念,其目的是通过和一些公有云的服务集成以达到更好的功能。

aws 环境下的 k8s 是指 k8s 集成了部分 aws 的服务方便集群的使用。到目前为止,我所知道的包含如下内容:

  1. ebs 做 persistent volume
  2. 通过 elb 为 LoadBalancer 类型的 service 绑定一个外部域名可以直接通过域名去访问

而这篇文章则是重点介绍如何让 k8s 去支持这些特性,当然会缺少 kubeadm 一些信息,会在另外一篇介绍。

配置

k8s 现在已经相当庞大,大的跟个操作系统似的。但是很遗憾其文档的成熟度还处于初级阶段,我甚至找不到 cloud-provider 配置的一些细节,唯一可靠的文档似乎就是代码,但是一不小心打开一个三四千行的 golang 文件我也是一脸迷茫...最后在一个诡异的 github issue 里面找到了一个 googledoc 解释了 cloud-provider=aws 的一些细节。

为 EC2 添加 IAM 权限

为了让 k8s 可以操纵 aws 的资源需要为 ec2 添加权限:

for master

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": [
				"ec2:*",
				"elasticloadbalancing:*",
				"ecr:GetAuthorizationToken",
				"ecr:BatchCheckLayerAvailability",
				"ecr:GetDownloadUrlForLayer",
				"ecr:GetRepositoryPolicy",
				"ecr:DescribeRepositories",
				"ecr:ListImages",
				"ecr:BatchGetImage",
				"autoscaling:DescribeAutoScalingGroups",
				"autoscaling:UpdateAutoScalingGroup"
			],
			"Resource": "*"
		}
	]
}

for worker

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": [
				"ec2:Describe*",
				"ecr:GetAuthorizationToken",
				"ecr:BatchCheckLayerAvailability",
				"ecr:GetDownloadUrlForLayer",
				"ecr:GetRepositoryPolicy",
				"ecr:DescribeRepositories",
				"ecr:ListImages",
				"ecr:BatchGetImage"
			],
			"Resource": "*"
		}
	]
}

为 ec2 机器添加标签

为了识别具体哪些 ec2 是集群的一部分需要为每个 ec2 添加一个 KubernetesCluster 的 tag。

更新 kubelet config

为了支持 cloud-provider 首先需要在 kubelet 的配置里做相应修改,为 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 添加 KUBELET_EXTRA_ARGS:

[Service]
Environment="KUBELET_EXTRA_ARGS=--cloud-provider=aws

更新 kubeadm 配置

apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
cloudProvider: aws
api:
  advertiseAddress: <internal-ip-address>
apiServerCertSANs:
- <public-ip-address>
- <public-hostname>

在用 kubeadm 创建集群时采用命令即可

kubeadm init --config=init-config.yaml

更新 hostname

首先先要说明一下,aws 的机器默认的 hostname 是和其 internal dns hostname 不同。默认的是这个样子:

ip-xx-xx-xx-xx

而 internal hostname 则是

ip-xx-xx-xx-xx.<region>.compute.internal

之类的东西。而 aws kubernetes 需要通过这个 internal dns hostname 去定位 node 所以需要将 hostname 更改成这个。

先前可以通过 `--hostname-override`` 参数覆盖默认的 aws hostname,但是最近似乎不行了,不如直接修改 hostname 一劳永逸。

curl http://169.254.169.254/latest/meta-data/local-hostname > /etc/hostname

然后重启机器使得更新起效。

注意

如果一开始配置坏了需要重新配置那一定要 kubeadm reset 哦:

kubeadm reset
ifconfig cni0 down
ip link delete cni0
ifconfig flannel.1 down
ip link delete flannel.1
rm -rf /var/lib/cni/

因为我自己安装的 flannel 其他的要看情况修改哦。

相关资料

  1. aws cloud provider
  2. kubeadm
  3. kubeadm init
  4. Kubernetes on AWS