<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; CephFS</title>
	<atom:link href="http://tonybai.com/tag/cephfs/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Mon, 20 Apr 2026 23:16:50 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>在Kubernetes集群上部署高可用Harbor镜像仓库</title>
		<link>https://tonybai.com/2017/12/08/deploy-high-availability-harbor-on-kubernetes-cluster/</link>
		<comments>https://tonybai.com/2017/12/08/deploy-high-availability-harbor-on-kubernetes-cluster/#comments</comments>
		<pubDate>Fri, 08 Dec 2017 06:14:54 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[adminserver]]></category>
		<category><![CDATA[Ceph]]></category>
		<category><![CDATA[CephFS]]></category>
		<category><![CDATA[configmap]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[galera]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[harbor]]></category>
		<category><![CDATA[High-Available]]></category>
		<category><![CDATA[image]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[mysqldump]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[pod]]></category>
		<category><![CDATA[PV]]></category>
		<category><![CDATA[PVC]]></category>
		<category><![CDATA[redis]]></category>
		<category><![CDATA[registry]]></category>
		<category><![CDATA[secret]]></category>
		<category><![CDATA[Service]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[镜像]]></category>
		<category><![CDATA[镜像仓库]]></category>
		<category><![CDATA[高可用]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2477</guid>
		<description><![CDATA[关于基于Harbor的高可用私有镜像仓库，在我的博客里曾不止一次提到，在源创会2017沈阳站上，我还专门以此题目和大家做了分享。事后，很多人通过微博私信、个人公众号或博客评论问我是否可以在Kubernetes集群上安装高可用的Harbor仓库，今天我就用这篇文章来回答大家这个问题。 一、Kubernetes上的高可用Harbor方案 首先，我可以肯定给出一个回答：Harbor支持在Kubernetes部署。只不过Harbor官方的默认安装并非是高可用的，而是“单点式”的。在《基于Harbor的高可用企业级私有容器镜像仓库部署实践》一文中，我曾谈到了一种在裸机或VM上的、基于Cephfs共享存储的高可用Harbor方案。在Kubernetes上部署，其高可用的思路也是类似的，可见下面这幅示意图： 围绕这幅示意图，简单说明一下我们的方案： 通过在Kubernetes上启动Harbor内部各组件的多个副本的方式实现Harbor服务的计算高可用； 通过挂载CephFS共享存储的方式实现镜像数据高可用； Harbor使用的配置数据和关系数据放在外部(External)数据库集群中，保证数据高可用和实时一致性； 通过外部Redis集群实现UI组件的session共享。 方案确定后，接下来我们就开始部署。 二、环境准备 在Harbor官方的对Kubernetes支持的说明中，提到当前的Harbor on kubernetes相关脚本和配置在Kubernetes v1.6.5和Harbor v1.2.0上验证测试通过了，因此在我们的实验环境中，Kubernetes至少要准备v1.6.5及以后版本。下面是我的环境的一些信息： Kubernetes使用v1.7.3版本： # kubelet --version Kubernetes v1.7.3 Docker使用17.03.2版本： # docker version Client: Version: 17.03.2-ce API version: 1.27 Go version: go1.7.5 Git commit: f5ec1e2 Built: Tue Jun 27 03:35:14 2017 OS/Arch: linux/amd64 Server: Version: 17.03.2-ce API version: 1.27 (minimum version 1.12) Go [...]]]></description>
			<content:encoded><![CDATA[<p>关于<a href="http://tonybai.com/2017/06/09/setup-a-high-availability-private-registry-based-on-harbor-and-cephfs/">基于Harbor的高可用私有镜像仓库</a>，在我的博客里<a href="http://tonybai.com/2017/06/15/fix-auth-fail-when-login-harbor-registry/">曾不止一次提到</a>，在<a href="http://tonybai.com/2017/10/24/go-evolution-for-ten-years-an-interview-by-osc/">源创会2017沈阳站</a>上，我还专门<a href="http://tonybai.com/2017/10/23/the-speech-script-practice-on-deploying-a-ha-harbor-cluster-for-osc-shenyang-2017/">以此题目和大家做了分享</a>。事后，很多人通过<a href="https://weibo.com/bigwhite20xx">微博私信</a>、<a href="https://mp.weixin.qq.com/mp/qrcode?scene=10000004&amp;size=102&amp;__biz=MzIyNzM0MDk0Mg==&amp;mid=2247483828&amp;idx=1&amp;sn=d8bcc352a0ad2fdb5e02f3a2c40c4b2b&amp;send_time=">个人公众号</a>或博客评论问我是否可以在<a href="http://tonybai.com/tag/kubernetes">Kubernetes集群</a>上安装高可用的<a href="https://github.com/vmware/harbor">Harbor</a>仓库，今天我就用这篇文章来回答大家这个问题。</p>
<h2>一、Kubernetes上的高可用Harbor方案</h2>
<p>首先，我可以肯定给出一个回答：Harbor支持在Kubernetes部署。只不过Harbor官方的默认安装并非是高可用的，而是“单点式”的。在<a href="http://tonybai.com/2017/10/23/the-speech-script-practice-on-deploying-a-ha-harbor-cluster-for-osc-shenyang-2017/">《基于Harbor的高可用企业级私有容器镜像仓库部署实践》</a>一文中，我曾谈到了一种在裸机或VM上的、基于<a href="http://tonybai.com/2017/05/08/mount-cephfs-acrossing-nodes-in-kubernetes-cluster/">Cephfs</a>共享存储的高可用Harbor方案。在Kubernetes上部署，其高可用的思路也是类似的，可见下面这幅示意图：</p>
<p><img src="http://tonybai.com/wp-content/uploads/harbor-on-k8s-arch.png" alt="img{512x368}" /></p>
<p>围绕这幅示意图，简单说明一下我们的方案：</p>
<ul>
<li>通过在Kubernetes上启动Harbor内部各组件的多个副本的方式实现Harbor服务的计算高可用；</li>
<li>通过挂载CephFS共享存储的方式实现镜像数据高可用；</li>
<li>Harbor使用的配置数据和关系数据放在外部(External)数据库集群中，保证数据高可用和实时一致性；</li>
<li>通过外部<a href="https://redis.io/">Redis</a>集群实现UI组件的session共享。</li>
</ul>
<p>方案确定后，接下来我们就开始部署。</p>
<h2>二、环境准备</h2>
<p>在Harbor官方的<a href="https://github.com/vmware/harbor/blob/master/docs/kubernetes_deployment.md">对Kubernetes支持的说明</a>中，提到当前的Harbor on kubernetes相关脚本和配置在Kubernetes v1.6.5和Harbor v1.2.0上验证测试通过了，因此在我们的实验环境中，<a href="http://tonybai.com/tag/kubernetes">Kubernetes</a>至少要准备v1.6.5及以后版本。下面是我的环境的一些信息：</p>
<pre><code>Kubernetes使用v1.7.3版本：

# kubelet --version
Kubernetes v1.7.3

Docker使用17.03.2版本：

# docker version
Client:
 Version:      17.03.2-ce
 API version:  1.27
 Go version:   go1.7.5
 Git commit:   f5ec1e2
 Built:        Tue Jun 27 03:35:14 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.03.2-ce
 API version:  1.27 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   f5ec1e2
 Built:        Tue Jun 27 03:35:14 2017
 OS/Arch:      linux/amd64
 Experimental: false

</code></pre>
<p>关于Harbor的相关脚本，我们直接用master branch中的，而不是v1.2.0这个release版本中的。<strong>切记</strong>！否则你会发现v1.2.0版本源码中的相关kubernetes支持脚本根本就没法工作，甚至缺少adminserver组件的相关脚本。不过Harbor相关组件的image版本，我们使用的还是<strong>v1.2.0</strong>的：</p>
<pre><code>Harbor源码的版本：

commit 82d842d77c01657589d67af0ea2d0c66b1f96014
Merge pull request #3741 from wy65701436/add-tc-concourse   on Dec 4, 2017

Harbor各组件的image的版本：

REPOSITORY                      TAG                 IMAGE ID
vmware/harbor-jobservice      v1.2.0          1fb18427db11
vmware/harbor-ui              v1.2.0          b7069ac3bd4b
vmware/harbor-adminserver     v1.2.0          a18331f0c1ae
vmware/registry               2.6.2-photon    c38af846a0da
vmware/nginx-photon           1.11.13         2971c92cc1ae
</code></pre>
<p>除此之外，高可用Harbor使用外部的DB cluster和redis cluster，DB cluster我们采用MySQL，对于MySQL cluster，可以使用<a href="http://galeracluster.com/products/">mysql galera cluster</a>或MySQL5.7以上版本自带的Group Replication (MGR) 集群。</p>
<h2>三、探索harbor on k8s部署脚本和配置</h2>
<p>我们在本地创建harbor-install-on-k8s目录，并将Harbor最新源码下载到该目录下：</p>
<pre><code># mkdir harbor-install-on-k8s
# cd harbor-install-on-k8s
# wget -c https://github.com/vmware/harbor/archive/master.zip
# unzip master.zip
# cd harbor-master
# ls -F
AUTHORS  CHANGELOG.md  contrib/  CONTRIBUTING.md  docs/
LICENSE  make/  Makefile  NOTICE  partners.md  README.md
ROADMAP.md  src/  tests/  tools/  VERSION

</code></pre>
<p>将Harbor部署到k8s上的脚本就在make/kubernetes目录下：</p>
<pre><code># cd harbor-master/make
# tree kubernetes
kubernetes
├── adminserver
│   ├── adminserver.rc.yaml
│   └── adminserver.svc.yaml
├── jobservice
│   ├── jobservice.rc.yaml
│   └── jobservice.svc.yaml
├── k8s-prepare
├── mysql
│   ├── mysql.rc.yaml
│   └── mysql.svc.yaml
├── nginx
│   ├── nginx.rc.yaml
│   └── nginx.svc.yaml
├── pv
│   ├── log.pvc.yaml
│   ├── log.pv.yaml
│   ├── registry.pvc.yaml
│   ├── registry.pv.yaml
│   ├── storage.pvc.yaml
│   └── storage.pv.yaml
├── registry
│   ├── registry.rc.yaml
│   └── registry.svc.yaml
├── templates
│   ├── adminserver.cm.yaml
│   ├── jobservice.cm.yaml
│   ├── mysql.cm.yaml
│   ├── nginx.cm.yaml
│   ├── registry.cm.yaml
│   └── ui.cm.yaml
└── ui
    ├── ui.rc.yaml
    └── ui.svc.yaml

8 directories, 25 files

</code></pre>
<ul>
<li>k8s-prepare脚本：根据templates下的模板文件以及harbor.cfg中的配置生成各个组件，比如registry等的最终configmap配置文件。它的作用类似于用docker-compose工具部署Harbor时的prepare脚本；</li>
<li>templates目录：templates目录下放置各个组件的配置模板文件（configmap文件模板），将作为k8s-prepare的输入；</li>
<li>pv目录：Harbor组件所使用的存储插件的配置，默认情况下使用hostpath，对于高可用Harbor而言，我们这里将使用cephfs；</li>
<li>其他组件目录，比如：registry：这些目录中存放这各个组件的service yaml和rc yaml，用于在Kubernetes cluster启动各个组件时使用。</li>
</ul>
<p>下面我用一个示意图来形象地描述一下配置的生成过程以及各个文件在后续Harbor组件启动中的作用：</p>
<p><img src="http://tonybai.com/wp-content/uploads/harbor-on-k8s-config-generating-and-components-launch-flow.png" alt="img{512x368}" /></p>
<p>由于使用external mysql db，Harbor自带的mysql组件我们不会使用，对应的pv目录下的storage.pv.yaml和storage.pvc.yaml我们也不会去关注和使用。</p>
<h2>四、部署步骤</h2>
<h3>1、配置和创建挂载Cephfs的pv和pvc</h3>
<p>我们先在共享分布式存储CephFS上为Harbor的存储需求创建目录：apps/harbor-k8s，并在harbor-k8s下创建两个子目录：log和registry，分别满足jobservice和registry的存储需求：</p>
<pre><code># cd /mnt   // CephFS的根目录挂载到了/mnt下面
# mkdir -p apps/harbor-k8s/log
# mkdir -p apps/harbor-k8s/registry
# tree apps/harbor-k8s
apps/harbor-k8s
├── log
└── registry
</code></pre>
<p>关于CephFS的挂载等具体操作步骤，可以参见我的<a href="http://tonybai.com/2017/05/08/mount-cephfs-acrossing-nodes-in-kubernetes-cluster/">《Kubernetes集群跨节点挂载CephFS》</a>一文。</p>
<p>接下来，创建用于k8s pv挂载cephfs的ceph-secret，我们编写一个ceph-secret.yaml文件：</p>
<pre><code>//ceph-secret.yaml
apiVersion: v1
data:
  key: {base64 encoding of the ceph admin.secret}
kind: Secret
metadata:
  name: ceph-secret
type: Opaque
</code></pre>
<p>创建ceph-secret：</p>
<pre><code># kubectl create -f ceph-secret.yaml
secret "ceph-secret" created
</code></pre>
<p>最后，我们来修改pv、pvc文件并创建对应的pv和pvc资源，要修改的文件包括pv/log.xxx和pv/registry.xxx，我们的目的就是用cephfs替代原先的hostPath：</p>
<pre><code>//log.pv.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: log-pv
  labels:
    type: log
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  cephfs:
    monitors:
      - {ceph-mon-node-ip}:6789
    path: /apps/harbor-k8s/log
    user: admin
    secretRef:
      name: ceph-secret
    readOnly: false
  persistentVolumeReclaimPolicy: Retain

//log.pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: log-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  selector:
    matchLabels:
      type: log

// registry.pv.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: registry-pv
  labels:
    type: registry
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  cephfs:
    monitors:
      - 10.47.217.91:6789
    path: /apps/harbor-k8s/registry
    user: admin
    secretRef:
      name: ceph-secret
    readOnly: false
  persistentVolumeReclaimPolicy: Retain

//registry.pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: registry-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  selector:
    matchLabels:
      type: registry
</code></pre>
<p>创建pv和pvc：</p>
<pre><code># kubectl create -f log.pv.yaml
persistentvolume "log-pv" created
# kubectl create -f log.pvc.yaml
persistentvolumeclaim "log-pvc" created
# kubectl create -f registry.pv.yaml
persistentvolume "registry-pv" created
# kubectl create -f registry.pvc.yaml
persistentvolumeclaim "registry-pvc" created
# kubectl get pvc
NAME           STATUS    VOLUME        CAPACITY   ACCESSMODES   STORAGECLASS   AGE
log-pvc        Bound     log-pv        1Gi        RWX                          31s
registry-pvc   Bound     registry-pv   5Gi        RWX                          2s
# kubectl get pv
NAME          CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM                  STORAGECLASS   REASON    AGE
log-pv        1Gi        RWX           Retain          Bound     default/log-pvc                                 36s
registry-pv   5Gi        RWX           Retain          Bound     default/registry-pvc                            6s
</code></pre>
<h3>2、创建和初始化Harbor用的数据库</h3>
<p>我们需要在External DB中创建Harbor访问数据库所用的user(harbork8s/harbork8s)以及所使用的数据库(registry_k8s)：</p>
<pre><code>mysql&gt; create user harbork8s identified  by 'harbork8s';
Query OK, 0 rows affected (0.03 sec)

mysql&gt; GRANT ALL PRIVILEGES ON *.* TO 'harbork8s'@'%' IDENTIFIED BY 'harbork8s' WITH GRANT OPTION;
Query OK, 0 rows affected, 1 warning (0.00 sec)

# mysql&gt; create database registry_k8s;
Query OK, 1 row affected (0.00 sec)

mysql&gt; grant all on registry_k8s.* to 'harbork8s' identified by 'harbork8s';
Query OK, 0 rows affected, 1 warning (0.00 sec)

</code></pre>
<p>由于目前Harbor还不支持自动init数据库，因此我们需要为新建的registry_k8s数据库做初始化，具体的方案就是先使用docker-compose工具在本地启动一个harbor，通过mysqldump将harbor-db container中的数据表dump出来，再导入到external db中的registry_k8s中，具体操作步骤如下：</p>
<pre><code># wget -c http://harbor.orientsoft.cn/harbor-1.2.0/harbor-offline-installer-v1.2.0.tgz
# tar zxvf harbor-offline-installer-v1.2.0.tgz

进入harbor目录，修改harbor.cfg中的hostname:

hostname = hub.tonybai.com:31777

# ./prepare
# docker-compose up -d

找到harbor_db的container id: 77fde71390e7，进入容器，并将数据库registry dump出来：

# docker exec -i -t  77fde71390e7 bash
# mysqldump -u root -pxxx --databases registry &gt; registry.dump

离开容器，将容器内导出的registry.dump copy到本地：
# docker cp 77fde71390e7:/tmp/registry.dump ./

修改registry.dump为registry_k8s.dump，修改其内容中的registry为registry_k8s，然后导入到external db：

# mysqldump -h external_db_ip -P 3306 -u harbork8s -pharbork8s
mysql&gt; source ./registry_k8s.dump;

</code></pre>
<h3>3、配置make/harbor.cfg</h3>
<p>harbor.cfg是整个配置生成的重要输入，我们在k8s-prepare执行之前，先要根据我们的需要和环境对harbor.cfg进行配置：</p>
<pre><code>// make/harbor.cfg
hostname = hub.tonybai.com:31777
db_password = harbork8s
db_host = {external_db_ip}
db_user = harbork8s
</code></pre>
<h3>4、对templates目录下的configmap配置模板(*.cm.yaml)进行配置调整</h3>
<ul>
<li>templates/adminserver.cm.yaml:</li>
</ul>
<pre><code>MYSQL_HOST: {external_db_ip}
MYSQL_USR: harbork8s
MYSQL_DATABASE: registry_k8s
RESET: "true"
</code></pre>
<p>注：adminserver.cm.yaml没有使用harbor.cfg中的有关数据库的配置项，而是需要单独再配置一遍，这块估计将来会fix掉这个问题。</p>
<ul>
<li>templates/registry.cm.yaml:</li>
</ul>
<pre><code>rootcertbundle: /etc/registry/root.crt
</code></pre>
<ul>
<li>templates/ui.cm.yaml:</li>
</ul>
<p>ui组件需要添加session共享。ui组件读取_REDIS_URL环境变量：</p>
<pre><code>//vmware/harbor/src/ui/main.go
... ..
    redisURL := os.Getenv("_REDIS_URL")
    if len(redisURL) &gt; 0 {
        beego.BConfig.WebConfig.Session.SessionProvider = "redis"
        beego.BConfig.WebConfig.Session.SessionProviderConfig = redisURL
    }
... ...

而redisURL的格式在beego的源码中有说明：

// beego/session/redis/sess_redis.go

// SessionInit init redis session
// savepath like redis server addr,pool size,password,dbnum
// e.g. 127.0.0.1:6379,100,astaxie,0
func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {...}
</code></pre>
<p>因此，我们在templates/ui.cm.yaml中添加一行：</p>
<pre><code>_REDIS_URL: {redis_ip}:6379,100,{redis_password},11
</code></pre>
<p>jobservice.cm.yaml和nginx.cm.yaml无需改变。</p>
<h3>5、对各组件目录下的xxx.rc.yaml和xxx.svc.yaml配置模板进行配置调整</h3>
<ul>
<li>adminserver/adminserver.rc.yaml</li>
</ul>
<pre><code>replicas: 3
</code></pre>
<ul>
<li>adminserver/adminserver.svc.yaml</li>
</ul>
<p>不变。</p>
<ul>
<li>jobservice/jobservice.rc.yaml、jobservice/jobservice.svc.yaml</li>
</ul>
<p>不变。</p>
<ul>
<li>nginx/nginx.rc.yaml</li>
</ul>
<pre><code>replicas: 3
</code></pre>
<ul>
<li>nginx/nginx.svc.yaml</li>
</ul>
<pre><code>apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      nodePort: 31777
      protocol: TCP
  selector:
    name: nginx-apps
</code></pre>
<ul>
<li>registry/registry.rc.yaml</li>
</ul>
<pre><code>replicas: 3
mountPath: /etc/registry
</code></pre>
<p><strong>这里有一个严重的<a href="https://github.com/vmware/harbor/issues/3637">bug</a></strong>，即registry.rc.yaml中configmap的默认mount路径：/etc/docker/registry与registry的docker image中的registry配置文件的路径/etc/registry不一致，这将导致我们精心配置的registry的configmap根本没有发挥作用，数据依然在memory中，而不是在我们配置的Cephfs中。这样一旦registry container退出，仓库的image数据就会丢失。同时也无法实现数据的高可用。因此，我们将mountPath都改为与registry image的一致，即：/etc/registry目录。</p>
<ul>
<li>registry/registry.svc.yaml</li>
</ul>
<p>不变。</p>
<ul>
<li>ui/ui.rc.yaml</li>
</ul>
<pre><code>replicas: 3
</code></pre>
<ul>
<li>ui/ui.svc.yaml</li>
</ul>
<pre><code>- name: _REDIS_URL
             valueFrom:
               configMapKeyRef:
                 name: harbor-ui-config
                 key: _REDIS_URL
</code></pre>
<h3>6、执行k8s-prepare</h3>
<p>执行k8s-prepare，生成各个组件的configmap文件：</p>
<pre><code># ./k8s-prepare
# git status
 ... ...

    adminserver/adminserver.cm.yaml
    jobservice/jobservice.cm.yaml
    mysql/mysql.cm.yaml
    nginx/nginx.cm.yaml
    registry/registry.cm.yaml
    ui/ui.cm.yaml
</code></pre>
<h3>7、启动Harbor组件</h3>
<ul>
<li>创建configmap</li>
</ul>
<pre><code># kubectl apply -f jobservice/jobservice.cm.yaml
configmap "harbor-jobservice-config" created
# kubectl apply -f nginx/nginx.cm.yaml
configmap "harbor-nginx-config" created
# kubectl apply -f registry/registry.cm.yaml
configmap "harbor-registry-config" created
# kubectl apply -f ui/ui.cm.yaml
configmap "harbor-ui-config" created
# kubectl apply -f adminserver/adminserver.cm.yaml
configmap "harbor-adminserver-config" created

# kubectl get cm
NAME                        DATA      AGE
harbor-adminserver-config   42        14s
harbor-jobservice-config    8         16s
harbor-nginx-config         3         16s
harbor-registry-config      2         15s
harbor-ui-config            9         15s
</code></pre>
<ul>
<li>创建harbor各组件对应的k8s service</li>
</ul>
<pre><code># kubectl apply -f jobservice/jobservice.svc.yaml
service "jobservice" created
# kubectl apply -f nginx/nginx.svc.yaml
service "nginx" created
# kubectl apply -f registry/registry.svc.yaml
service "registry" created
# kubectl apply -f ui/ui.svc.yaml
service "ui" created
# kubectl apply -f adminserver/adminserver.svc.yaml
service "adminserver" created

# kubectl get svc
NAME               CLUSTER-IP      EXTERNAL-IP   PORT(S)
adminserver        10.103.7.8      &lt;none&gt;        80/TCP
jobservice         10.104.14.178   &lt;none&gt;        80/TCP
nginx              10.103.46.129   &lt;nodes&gt;       80:31777/TCP
registry           10.101.185.42   &lt;none&gt;        5000/TCP,5001/TCP
ui                 10.96.29.187    &lt;none&gt;        80/TCP
</code></pre>
<ul>
<li>创建rc，启动各个组件pods</li>
</ul>
<pre><code># kubectl apply -f registry/registry.rc.yaml
replicationcontroller "registry-rc" created
# kubectl apply -f jobservice/jobservice.rc.yaml
replicationcontroller "jobservice-rc" created
# kubectl apply -f ui/ui.rc.yaml
replicationcontroller "ui-rc" created
# kubectl apply -f nginx/nginx.rc.yaml
replicationcontroller "nginx-rc" created
# kubectl apply -f adminserver/adminserver.rc.yaml
replicationcontroller "adminserver-rc" created

#kubectl get pods
NAMESPACE     NAME                  READY     STATUS    RESTARTS   AGE
default       adminserver-rc-9pc78  1/1       Running   0          3m
default       adminserver-rc-pfqtv  1/1       Running   0          3m
default       adminserver-rc-w55sx  1/1       Running   0          3m
default       jobservice-rc-d18zk   1/1       Running   1          3m
default       nginx-rc-3t5km        1/1       Running   0          3m
default       nginx-rc-6wwtz        1/1       Running   0          3m
default       nginx-rc-dq64p        1/1       Running   0          3m
default       registry-rc-6w3b7     1/1       Running   0          3m
default       registry-rc-dfdld     1/1       Running   0          3m
default       registry-rc-t6fnx     1/1       Running   0          3m
default       ui-rc-0kwrz           1/1       Running   1          3m
default       ui-rc-kzs8d           1/1       Running   1          3m
default       ui-rc-vph6d           1/1       Running   1          3m

</code></pre>
<h2>五、验证与Troubleshooting</h2>
<h3>1、docker cli访问</h3>
<p>由于harbor默认使用了http访问，因此在docker login前先要将我们的仓库地址加到/etc/docker/daemon.json的insecure-registries中：</p>
<pre><code>///etc/docker/daemon.json
{
  "insecure-registries": ["hub.tonybai.com:31777"]
}
</code></pre>
<p>systemctl daemon-reload and restart后，我们就可以通过docker login登录新建的仓库了(初始密码：Harbor12345)：</p>
<pre><code> docker login hub.tonybai.com:31777
Username (admin): admin
Password:
Login Succeeded

</code></pre>
<h3>2、docker push &amp; pull</h3>
<p>我们测试上传一个busybox image：</p>
<pre><code># docker pull busybox
Using default tag: latest
latest: Pulling from library/busybox
0ffadd58f2a6: Pull complete
Digest: sha256:bbc3a03235220b170ba48a157dd097dd1379299370e1ed99ce976df0355d24f0
Status: Downloaded newer image for busybox:latest
# docker tag busybox:latest hub.tonybai.com:31777/library/busybox:latest
# docker push hub.tonybai.com:31777/library/busybox:latest
The push refers to a repository [hub.tonybai.com:31777/library/busybox]
0271b8eebde3: Preparing
0271b8eebde3: Pushing [==================================================&gt;] 1.338 MB
0271b8eebde3: Pushed
latest: digest: sha256:179cf024c8a22f1621ea012bfc84b0df7e393cb80bf3638ac80e30d23e69147f size: 527
</code></pre>
<p>下载刚刚上传的busybox:</p>
<pre><code># docker pull hub.tonybai.com:31777/library/busybox:latest
latest: Pulling from library/busybox
414e5515492a: Pull complete
Digest: sha256:179cf024c8a22f1621ea012bfc84b0df7e393cb80bf3638ac80e30d23e69147f
Status: Downloaded newer image for hub.tonybai.com:31777/library/busybox:latest
</code></pre>
<h3>3、访问Harbor UI</h3>
<p>在浏览器中打开http://hub.tonybai.com:31777，用admin/Harbor12345登录，如果看到下面页面，说明安装部署成功了：</p>
<p><img src="http://tonybai.com/wp-content/uploads/harbor-on-k8s-ui.png" alt="img{512x368}" /></p>
<h2>六、参考资料</h2>
<ul>
<li><a href="https://github.com/vmware/harbor/blob/master/docs/kubernetes_deployment.md">Integration with Kubernetes</a></li>
<li><a href="http://tonybai.com/2017/06/09/setup-a-high-availability-private-registry-based-on-harbor-and-cephfs/">基于Harbor和CephFS搭建高可用Private Registry</a></li>
</ul>
<hr />
<p>微博：<a href="http://weibo.com/bigwhite20xx">@tonybai_cn</a><br />
微信公众号：iamtonybai<br />
github.com: https://github.com/bigwhite</p>
<p>微信赞赏：<br />
<img src="http://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/12/08/deploy-high-availability-harbor-on-kubernetes-cluster/feed/</wfw:commentRss>
		<slash:comments>17</slash:comments>
		</item>
		<item>
		<title>源创会2017沈阳站讲稿：基于Harbor的高可用企业级私有容器镜像仓库部署实践</title>
		<link>https://tonybai.com/2017/10/23/the-speech-script-practice-on-deploying-a-ha-harbor-cluster-for-osc-shenyang-2017/</link>
		<comments>https://tonybai.com/2017/10/23/the-speech-script-practice-on-deploying-a-ha-harbor-cluster-for-osc-shenyang-2017/#comments</comments>
		<pubDate>Mon, 23 Oct 2017 08:28:35 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AD]]></category>
		<category><![CDATA[beego]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[CD]]></category>
		<category><![CDATA[Ceph]]></category>
		<category><![CDATA[CephFS]]></category>
		<category><![CDATA[cgroups]]></category>
		<category><![CDATA[CI]]></category>
		<category><![CDATA[daemon.json]]></category>
		<category><![CDATA[distribution]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[docker-compose]]></category>
		<category><![CDATA[docker-registry]]></category>
		<category><![CDATA[dotCloud]]></category>
		<category><![CDATA[DX]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GroupReplication]]></category>
		<category><![CDATA[harbor]]></category>
		<category><![CDATA[High-Available]]></category>
		<category><![CDATA[image]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[KVM]]></category>
		<category><![CDATA[LDAP]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[linux-container]]></category>
		<category><![CDATA[loadbalance]]></category>
		<category><![CDATA[LXC]]></category>
		<category><![CDATA[MGR]]></category>
		<category><![CDATA[mount]]></category>
		<category><![CDATA[MVC]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[namespaces]]></category>
		<category><![CDATA[osc]]></category>
		<category><![CDATA[pipeline]]></category>
		<category><![CDATA[portus]]></category>
		<category><![CDATA[redis]]></category>
		<category><![CDATA[registry]]></category>
		<category><![CDATA[Scaling]]></category>
		<category><![CDATA[SUSE]]></category>
		<category><![CDATA[unionfs]]></category>
		<category><![CDATA[vm]]></category>
		<category><![CDATA[vmware]]></category>
		<category><![CDATA[Xen]]></category>
		<category><![CDATA[伸缩]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[开发流水线]]></category>
		<category><![CDATA[开源中国]]></category>
		<category><![CDATA[持续交付]]></category>
		<category><![CDATA[持续集成]]></category>
		<category><![CDATA[虚拟机]]></category>
		<category><![CDATA[镜像仓库]]></category>
		<category><![CDATA[高可用]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2427</guid>
		<description><![CDATA[上周六开源中国的源创会在沈阳举办了一次技术活动，很荣幸以本地讲师的身份和大家交流了一个topic: “基于Harbor的高可用企业级私有容器镜像仓库部署实践”。之所以选择这个topic，是因为这是我们团队的项目实践心得。很多企业和组织在深入使用Docker之后，都会有类似的高可用私有容器仓库搭建的需求，于是我就把我们摸索的实践和填坑过程拿出来，用30分钟与大家分享一下。另外这算是一个入门级的分享，并未深入过多原理。以下就是本次分享的内容讲稿整理。如有不妥或不正确的地方，欢迎交流指正。 大家下午好，欢迎各位来到源创会沈阳站。在这里我也代表沈阳的IT人欢迎源创会来到沈阳，希望能有更多的像源创会这样的组织到沈阳举办技术活动。非常高兴能有这个机会在源创会这个平台上做分享， 今天和大家一起探讨的题目是：“基于Harbor的高可用企业级私有容器镜像仓库部署实践”。题目有些长，简单来说就是如何搭建一个好用的镜像仓库。 首先做个简单的自我介绍。我叫白明，东软(注：源创会这次活动的会场在东软沈阳园区)是我的主场，在这里工作很多年，目前就职东软云科技；Gopher一枚，近两年主要使用Go语言开发；技术译者，曾参与翻译过《七周七语言》一书；并且参与过智慧城市架构系列丛书的编著工作；GopherChina大会讲师，这里顺便说一下GopherChina大会，它是目前中国地区规模最大、水平最高的Go语言技术大会，一般每年4月份在北京或上海举行。希望有志于Go语言开发的开发者积极参与；Blogger，写博10多年，依旧笔耕不倦；目前主要从事Docker&#38;kubernetes的研究和实践。 当今，IT技术发展飞快。五年前， IT从业者口中谈论最多的技术是Virtual Machine，即虚拟化技术，人们经常争论的是到底是vmware的技术好,还是原生kvm技术稳定，又或是xen的技术完美。转眼间五年过去了，大家口中经常讨论的技术词汇发生了变化，越来越多的技术人在谈论Docker，谈论容器。 Docker是什么？ Docker这门技术非常热，但我们要透过现象看其本质： Docker技术并不是新技术，而是将已有技术进行了更好的整合和包装。 内核容器技术以一种完整形态最早出现在Sun公司的Solaris操作系统上，Solaris是当时最先进的服务器操作系统。2005年Solaris发布Solaris Container技术，从此开启了内核容器之门。 IT技术发展的趋势就是这样：商业有的，开源也要有。三年后，即2008年，以Google公司开发人员为主导的Linux Container，LXC功能在被merge到Linux内核。LXC是一种内核级虚拟化技术，主要基于namespaces和cgroup技术，实现共享一个os kernel前提下的进程资源隔离，为进程提供独立的虚拟执行环境，这样的一个虚拟的执行环境就是一个容器。本质上说，LXC容器与现在的Docker所提供容器是一样的。但是，当时LXC处于早期阶段，开发人员可能更为关注LXC的技术实现，而对开发体验方面有所忽略，导致LXC技术使用门槛较高，普通应用开发者学习、理解和使用它的心智负担较高，因此应用并不广泛。 这一情况一直持续到2013年，当时美国一家名不见经传的公司dotCloud发布了一款平台工具Docker，对外宣称可以实现：“build,ship and run any app and anywhere”。Docker实质上也是基于namespaces和cgroup技术的，Docker的创新之处在于其基于union fs技术定义了一套应用打包规范，真正将应用及其运行的所有依赖都封装到一个特定格式的文件中，这种文件就被称为image，即镜像文件。同时，Docker还提供了一套抽象层次更高的工具集，这套工具对dev十分友好，具有良好的开发体验(Developer eXperience)，开发者无需关心namespace, cgroups之类底层技术，即可很easy的启动一个承载着其应用的容器： Docker run ubuntu echo hello 因此， 从2013发布以来，Docker项目就像坐上了火箭，发展迅猛，目前已经是github上最火爆的开源项目之一。这里还要提一点就是：Docker项目是使用go语言开发的，Docker项目的成功，也或多或少得益于Go优异的开发效率和执行效率。 Docker技术的出现究竟给我们带来了哪些好处呢，个人觉得至少有以下三点： 交付标准化：Docker使得应用程序和依赖的运行环境真正绑定结合为一体，得之即用。这让开发人员、测试和运维实现了围绕同一交付物，保持开发交付上下文同步的能力，即“test what you write, ship what you test”； 执行高效化：应用的启动速度从原先虚拟机的分钟级缩短到容器的秒级甚至ms级，使得应用可以支持快速scaling伸缩； 资源集约化：与vm不同的是，Container共享一个内核，这使得一个container的资源消耗仅为进程级别或进程组级别。同时，容器的镜像也因为如此，其size可以实现的很小，最小可能不足1k，平均几十M。与vm动辄几百兆的庞大身段相比，具有较大优势。 有了image文件后，自然而言我们就有了对image进行存取和管理的需求，即我们需要一个镜像仓库，于是Docker推出了Docker registry这个项目。Docker Registry就是Docker image的仓库，用来存储、管理和分发image的；Docker registry由Docker公司实现，项目名为distribution，其实现了Docker Registr 2.0协议，与早前的Registry 1.x协议版本相比，Distribution采用Go语言替换了Python，在安全性和性能方面都有了大幅提升；Docker官方运行着一个世界最大的公共镜像仓库：hub.docker.com，最常用的image都在hub上，比如反向代理nginx、redis、ubuntu等。鉴于国内访问hub网速不佳，多使用国内容器服务厂商提供的加速器。Docker官方还将Registry本身打入到了一个image中，方便开发人员快速以容器形式启动一个Registry： docker run -d [...]]]></description>
			<content:encoded><![CDATA[<p>上周六<a href="http://www.oschina.net/">开源中国</a>的<a href="https://www.oschina.net/event/ych">源创会</a>在沈阳举办了一次技术活动，很荣幸以本地讲师的身份和大家交流了一个topic: “基于<a href="https://github.com/vmware/harbor">Harbor</a>的高可用企业级私有容器镜像仓库部署实践”。之所以选择这个topic，是因为这是我们团队的项目实践心得。很多企业和组织在深入使用Docker之后，都会有类似的高可用私有容器仓库搭建的需求，于是我就把我们摸索的实践和填坑过程拿出来，用30分钟与大家分享一下。另外这算是一个入门级的分享，并未深入过多原理。以下就是本次分享的内容讲稿整理。如有不妥或不正确的地方，欢迎交流指正。</p>
<p><img src="http://tonybai.com/wp-content/uploads/osc-shenyang-2017-1.jpg" alt="img{512x368}" /></p>
<p>大家下午好，欢迎各位来到源创会沈阳站。在这里我也代表沈阳的IT人欢迎源创会来到沈阳，希望能有更多的像源创会这样的组织到沈阳举办技术活动。非常高兴能有这个机会在源创会这个平台上做分享， 今天和大家一起探讨的题目是：“<a href="https://github.com/bigwhite/talks/tree/master/osc/2017">基于Harbor的高可用企业级私有容器镜像仓库部署实践</a>”。题目有些长，简单来说就是如何搭建一个好用的镜像仓库。</p>
<p><img src="http://tonybai.com/wp-content/uploads/osc-shenyang-2017-2.jpg" alt="img{512x368}" /></p>
<p>首先做个简单的自我介绍。我叫白明，东软(注：源创会这次活动的会场在东软沈阳园区)是我的主场，在这里工作很多年，目前就职东软云科技；<a href="https://golang.org/">Gopher</a>一枚，近两年主要使用Go语言开发；技术译者，曾参与翻译过《<a href="https://book.douban.com/subject/10555435/">七周七语言</a>》一书；并且参与过智慧城市架构系列丛书的编著工作；<a href="http://tonybai.com/2017/04/18/my-experience-of-gopherchina-2017-as-a-speaker/">GopherChina大会讲师</a>，这里顺便说一下<a href="http://gopherchina.org/">GopherChina大会</a>，它是目前中国地区规模最大、水平最高的Go语言技术大会，一般每年4月份在北京或上海举行。希望有志于<a href="http://tonybai.com/tag/go">Go语言</a>开发的开发者积极参与；Blogger，写博10多年，依旧笔耕不倦；目前主要从事<a href="http://tonybai.com/tag/docker">Docker</a>&amp;<a href="http://tonybai.com/tag/kubernetes">kubernetes</a>的研究和实践。</p>
<p>当今，IT技术发展飞快。五年前， IT从业者口中谈论最多的技术是<a href="https://en.wikipedia.org/wiki/Virtual_machine">Virtual Machine</a>，即虚拟化技术，人们经常争论的是到底是vmware的技术好,还是原生<a href="https://en.wikipedia.org/wiki/Kernel-based_Virtual_Machine">kvm</a>技术稳定，又或是<a href="https://en.wikipedia.org/wiki/Xen">xen</a>的技术完美。转眼间五年过去了，大家口中经常讨论的技术词汇发生了变化，越来越多的技术人在谈论<a href="https://en.wikipedia.org/wiki/Docker_%28software%29">Docker</a>，谈论容器。</p>
<p>Docker是什么？ Docker这门技术非常热，但我们要透过现象看其本质：</p>
<blockquote>
<p><strong>Docker技术并不是新技术，而是将已有技术进行了更好的整合和包装</strong>。</p>
</blockquote>
<p>内核容器技术以一种完整形态最早出现在<a href="https://en.wikipedia.org/wiki/Sun_Microsystems">Sun公司</a>的<a href="http://en.wikipedia.org/wiki/Solaris_(operating_system)">Solaris操作系统</a>上，Solaris是当时最先进的服务器操作系统。2005年Solaris发布<a href="https://en.wikipedia.org/wiki/Solaris_Containers">Solaris Container</a>技术，从此开启了内核容器之门。</p>
<p>IT技术发展的趋势就是这样：商业有的，开源也要有。三年后，即2008年，以Google公司开发人员为主导的<a href="https://en.wikipedia.org/wiki/LXC">Linux Container，LXC功能</a>在被merge到<a href="https://en.wikipedia.org/wiki/Linux_kernel">Linux内核</a>。LXC是一种内核级虚拟化技术，主要基于<a href="https://en.wikipedia.org/wiki/Linux_namespaces">namespaces</a>和<a href="https://en.wikipedia.org/wiki/Cgroups">cgroup</a>技术，实现共享一个os kernel前提下的进程资源隔离，为进程提供独立的虚拟执行环境，这样的一个虚拟的执行环境就是一个容器。本质上说，LXC容器与现在的Docker所提供容器是一样的。但是，当时LXC处于早期阶段，开发人员可能更为关注LXC的技术实现，而对开发体验方面有所忽略，导致LXC技术使用门槛较高，普通应用开发者学习、理解和使用它的心智负担较高，因此应用并不广泛。</p>
<p>这一情况一直持续到2013年，当时美国一家名不见经传的公司<a href="https://en.wikipedia.org/wiki/DotCloud">dotCloud</a>发布了一款平台工具Docker，对外宣称可以实现：“build,ship and run any app and anywhere”。Docker实质上也是基于namespaces和cgroup技术的，Docker的创新之处在于其基于<a href="https://en.wikipedia.org/wiki/Category:Union_file_systems">union fs技术</a>定义了一套应用打包规范，真正将应用及其运行的所有依赖都封装到一个特定格式的文件中，这种文件就被称为image，即镜像文件。同时，Docker还提供了一套抽象层次更高的工具集，这套工具对dev十分友好，具有良好的开发体验(Developer eXperience)，开发者无需关心namespace, cgroups之类底层技术，即可很easy的启动一个承载着其应用的容器：</p>
<pre><code>Docker run ubuntu echo hello
</code></pre>
<p>因此， 从2013发布以来，Docker项目就像坐上了火箭，发展迅猛，目前已经是github上最火爆的开源项目之一。这里还要提一点就是：Docker项目是使用go语言开发的，Docker项目的成功，也或多或少得益于Go优异的开发效率和执行效率。</p>
<p>Docker技术的出现究竟给我们带来了哪些好处呢，个人觉得至少有以下三点：</p>
<ul>
<li>交付标准化：Docker使得应用程序和依赖的运行环境真正绑定结合为一体，得之即用。这让开发人员、测试和运维实现了围绕同一交付物，保持开发交付上下文同步的能力，即“test what you write, ship what you test”；</li>
<li>执行高效化：应用的启动速度从原先虚拟机的分钟级缩短到容器的秒级甚至ms级，使得应用可以支持快速scaling伸缩；</li>
<li>资源集约化：与vm不同的是，Container共享一个内核，这使得一个container的资源消耗仅为进程级别或进程组级别。同时，容器的镜像也因为如此，其size可以实现的很小，最小可能不足1k，平均几十M。与vm动辄几百兆的庞大身段相比，具有较大优势。</li>
</ul>
<p>有了image文件后，自然而言我们就有了对image进行存取和管理的需求，即我们需要一个镜像仓库，于是Docker推出了Docker registry这个项目。Docker Registry就是Docker image的仓库，用来存储、管理和分发image的；Docker registry由Docker公司实现，项目名为<a href="https://github.com/docker/distribution">distribution</a>，其实现了Docker Registr 2.0协议，与早前的Registry 1.x协议版本相比，Distribution采用Go语言替换了Python，在安全性和性能方面都有了大幅提升；Docker官方运行着一个世界最大的公共镜像仓库：hub.docker.com，最常用的image都在hub上，比如反向代理nginx、redis、ubuntu等。鉴于国内访问hub网速不佳，多使用国内容器服务厂商提供的加速器。Docker官方还将Registry本身打入到了一个image中，方便开发人员快速以容器形式启动一个Registry：</p>
<pre><code>docker run -d -p 5000:5000 --restart=always --name registry registry:2
</code></pre>
<p>不过，这样启动的Registry更多仅仅是一个Demo级别或满足个体开发者自身需要的，离满足企业内部开发流程或生产需求还差了许多。</p>
<p>既然Docker官方运行着免费的镜像仓库，那我们还需要自己搭建吗？实际情况是，对Docker的使用越深入，对私有仓库的需求可能就越迫切。我们先来看一组Docker 2016官方的调查数据，看看Docker都应用在哪些场合。 从Docker 2016官方调查来看，Docker 更多用于dev、<a href="https://en.wikipedia.org/wiki/Continuous_integration">ci</a>和<a href="https://en.wikipedia.org/wiki/DevOps">DevOps</a>等环节，这三个场合下的应用占据了半壁江山。而相比于公共仓库，私有镜像仓库能更好的满足开发人员在这些场合对镜像仓库的需求。理由至少有四点：</p>
<ul>
<li>
<p>便于集成到内部CI/Cd<br />
以我司内部为例，由于公司内部办公需要使用正向代理访问外部网络，要想将Public Registry集成到你的内部CI中，技术上就会有很多坎儿，整个搭建过程可能是非常痛苦的；</p>
</li>
<li>
<p>对镜像可以更全面掌控<br />
一般来说，外部Public Registry提供的管理功能相对单一，往往无法满足企业内部的开发和交付需求；</p>
</li>
<li>
<p>内部网络，网络传输性能更好<br />
内部开发运维流水线很多环节是有一定的时间敏感性的，比如：一次CI如果因为network问题导致image pull总是timeout，会让dev非常闹心，甚至影响整体的开发和交付效率。</p>
</li>
<li>
<p>出于安全考虑<br />
总是有企业不想将自己开发的软件或数据放到公网上，因此在企业内部选择搭建一个private registry更会让这些企业得到满足；另外企业对仓库的身份验证可能还有LDAP支持的需求，这是外部registry无法满足的。</p>
</li>
</ul>
<p>一旦企业决定搭建自己的private仓库，那么就得做一个private仓库的技术选型。商业版不在我们讨论范围内，我们从开源软件中挑选。不过开源的可选的不多，Docker 官方的Registry更聚焦通用功能，没有针对企业客户需求定制，开源领域我们大致有两个主要候选者：<a href="https://github.com/SUSE/">SUSE</a>的<a href="https://github.com/SUSE/Portus">Portus</a>和Vmware的<a href="https://github.com/vmware/harbor">Harbor</a>。针对开源项目的技术选型，我个人的挑选原则最简单的就是看社区生态，落实到具体的指标上包括：</p>
<ul>
<li>项目关注度（即star数量）</li>
<li>社区对issue的反馈数量和积极性</li>
<li>项目维护者对issue fix的积极程度以及是否有远大的roadmap</li>
</ul>
<p>对比后，我发现在这三个指标上，目前Harbor都暂时领先portus一段距离，于是我们选择Harbor。</p>
<p>Harbor是VMware中国团队开源的企业级镜像仓库项目，聚焦镜像仓库的企业级需求，这里从其官网摘录一些特性，大家一起来看一下：</p>
<p>– 支持基于角色的访问控制RBAC;<br />
– 支持镜像复制策略(PUSH);<br />
– 支持无用镜像数据的自动回收和删除; – 支持LDAP/AD认证;<br />
– Web UI;<br />
– 提供审计日志功能;<br />
– 提供RESTful API,便于扩展;<br />
– 支持中文&amp;部署Easy。</p>
<p>不过，Harbor默认安装的是单实例仓库，并非是<a href="https://en.wikipedia.org/wiki/High_availability">高可用的</a>。对于接纳和使用Docker的企业来说，镜像仓库已经企业内部开发、交付和运维流水线的核心，一旦仓库停掉，流水线将被迫暂停，对开发交付的效率会产生重要影响；对于一些中大型企业组织，单实例的仓库性能也无法满足需求，为此高可用的Harbor势在必行。在设计Harbor HA方案之前，我们简单了解一下Harbor组成架构。</p>
<p>一个Harbor实例就是一组由<a href="https://github.com/docker/compose">docker-compose</a>工具启动的容器服务，主要包括四个主要组件：</p>
<ul>
<li>
<p>proxy<br />
实质就是一个反向代理<a href="http://tonybai.com/tag/nginx">nginx</a>，负责流量路由分担到ui和registry上；</p>
</li>
<li>
<p>registry<br />
这里的registry就是原生的docker官方的registry镜像仓库，Harbor在内部内置了一个仓库，所有仓库的核心功能均是由registry完成的；</p>
</li>
<li>
<p>core service<br />
包含了ui、token和webhook服务；</p>
</li>
<li>
<p>job service<br />
主要用于镜像复制供。</p>
</li>
</ul>
<p>同时，每个Harbor实例还启动了一个MySQL数据库容器，用于保存自身的配置和镜像管理相关的关系数据。</p>
<p>高可用系统一般考虑三方面：计算高可用、存储高可用和网络高可用。在这里我们不考虑网络高可用。基于Harbor的高可用仓库方案，这里列出两个。</p>
<p><img src="http://tonybai.com/wp-content/uploads/harbor-ha-solutions.png" alt="img{512x368}" /></p>
<p>两个方案的共同点是计算高可用，都是通过lb实现的多主热运行，保证无单点；存储高可用则各有各的方案。一个使用了分布式共享存储，数据可靠性由共享存储provider提供；另外一个则需要harbor自身逻辑参与，通过镜像相互复制的方式保持数据的多副本。</p>
<p>两种方案各有优缺点，就看哪种更适合你的组织以及你手里的资源是否能满足方案的搭建要求。</p>
<p>方案1是Harbor开发团队推荐的标准方案，由于基于分布式共享存储，因此其scaling非常好；同样，由于多Harbor实例共享存储，因此可以保持数据是实时一致的。方案1的不足也是很明显的，第一：门槛高，需要具备共享存储provider；第二搭建难度要高于第二个基于镜像复制的方案。</p>
<p>方案2的优点就是首次搭建简单。不足也很多：scaling差，甚至是不能，一旦有三个或三个以上节点，可能就会出现“环形复制”；镜像复制需要时间，因此存在多节点上数据周期性不一致的情况；Harbor的镜像复制规则以Project为单位配置，因此一旦新增Project，需要在每个节点上手工维护复制规则，非常繁琐。因此，我们选择方案1。</p>
<p>我们来看一下方案1的细节： 这是一幅示意图。</p>
<ul>
<li>每个安放harbor实例的node都mount cephfs。ceph是目前最流行的分布式共享存储方案之一；</li>
<li>每个node上的harbor实例（包含组件：ui、registry等）都volume mount node上的cephfs mount路径；</li>
<li>通过Load Balance将request流量负载到各个harbor实例上；</li>
<li>使用外部MySQL cluster替代每个Harbor实例内部自维护的那个MySQL容器；对于MySQL cluster，可以使用<a href="http://galeracluster.com/products/">mysql galera cluster</a>或MySQL5.7以上版本自带的Group Replication (MGR) 集群。</li>
<li>通过外部Redis实现访问Harbor ui的session共享，这个功能是Harbor UI底层MVC框架-<a href="https://github.com/astaxie/beego">beego</a>提供的。</li>
</ul>
<p>接下来，我们就来看具体的部署步骤和细节。</p>
<p>环境和先决条件：</p>
<ul>
<li>三台VM(Ubuntu 16.04及以上版本)；</li>
<li><a href="http://tonybai.com/2016/11/07/integrate-kubernetes-with-ceph-rbd/">CephFS</a>、MySQL、Redis已就绪；</li>
<li>Harbor v1.1.0及以上版本；</li>
<li>一个域名：hub.tonybai.com:8070。我们通过该域名和服务端口访问Harbor，我们可以通过dns解析多ip轮询实现最简单的Load balance，虽然不完美。</li>
</ul>
<h3>第一步：挂载cephfs</h3>
<p>每个安装Harbor instance的节点都要mount cephfs的相关路径，步骤包括：</p>
<pre><code>#安装cephfs内核驱动
apt install ceph-fs-common

# 修改/etc/fstab，添加挂载指令，保证节点重启依旧可以自动挂载cephfs
xx.xx.xx.xx:6789:/apps/harbor /mnt/cephfs/harbor ceph name=harbor,secretfile=/etc/ceph/a dmin.secret,noatime,_netdev 0 2
</code></pre>
<p>这里涉及一个密钥文件admin.secret，这个secret文件可以在ceph集群机器上使用ceph auth tool生成。</p>
<p><img src="http://tonybai.com/wp-content/uploads/harbor-prepare-process.png" alt="img{512x368}" /></p>
<p>前面提到过每个Harbor实例都是一组容器服务，这组容器启动所需的配置文件是在Harbor正式启动前由prepare脚本生成的，Prepare脚本生成过程的输入包括：harbor.cfg、docker-compose.yml和common/templates下的配置模板文件。这也是部署高可用Harbor的核心步骤，我们逐一来看。</p>
<h3>第二步：修改harbor.cfg</h3>
<p>我们使用域名访问Harbor，因此我们需要修改hostname配置项。注意如果要用域名访问，这里一定填写域名，否则如果这里使用的是Harbor node的IP，那么在后续会存在client端和server端仓库地址不一致的情况；</p>
<p>custom_crt=false 关闭 crt生成功能。注意：三个node关闭其中两个，留一个生成一套数字证书和私钥。</p>
<h3>第三步：修改docker-compose.yml</h3>
<p>docker-compose.yml是docker-compose工具标准配置文件，用于配置docker-compose即将启动的容器服务。针对该配置文件，我们主要做三点修改：</p>
<ul>
<li>修改volumes路径<br />
由/data/xxx 改为：/mnt/cephfs/harbor/data/xxx</li>
<li>由于使用外部Mysql，因此需要删除mysql service以及其他 service对mysql service的依赖 (depends_on)</li>
<li>修改对proxy外服务端口 ports:  8070:80</li>
</ul>
<h3>第四步：配置访问external mysql和redis</h3>
<p>external mysql的配置在common/templates/adminserver/env中，我们用external Mysql的访问方式覆盖下面四项配置：</p>
<pre><code>MYSQL_HOST=harbor_host
MYSQL_PORT=3306
MYSQL_USR=harbor
MYSQL_PWD=harbor_password

</code></pre>
<p>还有一个关键配置，那就是将RESET由false改为true。<a href="http://tonybai.com/2017/06/09/setup-a-high-availability-private-registry-based-on-harbor-and-cephfs/">只有改为true，adminserver启动时，才能读取更新后的配置</a>：</p>
<pre><code>RESET=true
</code></pre>
<p>Redis连接的配置在common/templates/ui/env中，我们需要新增一行：</p>
<pre><code>_REDIS_URL=redis_ip:6379,100,password,0
</code></pre>
<h3>第五步：prepare并启动harbor</h3>
<p>执行prepare脚本生成harbor各容器服务的配置；在每个Harbor node上通过下面命令启动harbor实例：</p>
<pre><code>docker-compose up -d

</code></pre>
<p>启动后，可以通过docker-compose ps命令查看harbor实例中各容器的启动状态。如果启动顺利，都是”Up”状态，那么我们可以在浏览器里输入：http://hub.tonybai.com:8070，不出意外的话，我们就可以看到Harbor ui的登录页面了。</p>
<p>至此，我们的高可用Harbor cluster搭建过程就告一段落了。</p>
<h3>Troubleshooting</h3>
<p>不过，对Harbor的认知还未结束，我们在后续使用Harbor的过程中遇到了一些问题，这里举两个例子。</p>
<h4>问题1： docker login hub.tonybai.com:8070 failed</h4>
<p>现象日志：</p>
<pre><code>Error response from daemon: Get https://hub.tonybai.com:8070/v1/users/: http: server gave HTTP response to HTTPS client
</code></pre>
<p>通过错误日志分析应该是docker daemon与镜像仓库所用协议不一致导致。docker engine默认采用https协议访问仓库，但之前我们搭建的Harbor采用的是http协议提供服务，两者不一致。</p>
<p>解决方法有两种，这里列出第一种：让docker引擎通过http方式访问harbor仓库：</p>
<pre><code>在/etc/docker/daemon.json中添加insecure-registry：

{
    "insecure-registries": ["hub.tonybai.com:8070"]
}

重启docker service生效
</code></pre>
<p>第二种方法就是让Harbor支持https，需要为harbor的proxy配置私钥和证书，位置：harbor.cfg中</p>
<pre><code>#The path of cert and key files for nginx, they are applied only the protocol is set to https
ssl_cert = /data/cert/server.crt
ssl_cert_key = /data/cert/server.key
</code></pre>
<p>这里就不细说了。</p>
<h4>问题2：docker login hub.tonybai.com:8070 有时成功，有时failed</h4>
<p>现象日志:</p>
<pre><code>第一次登录成功：
# docker login -u user -p passwd http://hub.tonybai.com:8070 Login Succeeded

第二次登录失败：
# docker login -u user -p passwd http://hub.tonybai.com:8070
Error response from daemon: login attempt to http://hub.tonybai.com:8070/v2/ failed with status: 401 Unauthorized
</code></pre>
<p>这个问题的原因在于对docker registry v2协议登录过程理解不够透彻。docker registry v2是一个两阶段登录的过程：</p>
<ul>
<li>首先：docker client会到registry去尝试登录，registry发现request中没有携带token，则返回失败应答401，并告诉客户端到哪里去获取token；</li>
<li>客户端收到应答后，获取应答中携带的token service地址，然后到harbor的core services中的token service那里获取token（使用user, password进行校验）。一旦token service校验ok，则会使用private_key.pem生成一个token；</li>
<li>客户端拿到token后，再次到registry那里去登录，这次registry用root.crt去校验客户端携带的token，校验通过，则login成功。</li>
</ul>
<p>由于我们是一个harbor cluster，如果docker client访问的token service和registry是在一个harbor实例中的，那么login就会ok；否则docker client就会用harbor node1上token service生成的token到harbor node2上的registry去登录，由于harbor node2上root.crt与harbor node1上private_key.pem并非一对，因此<a href="http://tonybai.com/2017/06/15/fix-auth-fail-when-login-harbor-registry/">登录失败</a>。</p>
<p>解决方法：将所有节点上使用同一套root.crt和private_key.pem。即将一个harbor node（harbor.cfg中custom_crt=true的那个）上的 common/config/ui/private_key.pem和 common/config/registry/root.crt复制到其他harbor node;然后重建各harbor实例中的容器。</p>
<p>至此，我们的高可用Harbor仓库部署完了。针对上面的配置过程，我还做了几个录屏文件，由于时间关系，这里不能播放了，大家可以在下面这个连接下载并自行播放收看。</p>
<pre><code>Harbor install 录屏: https://pan.baidu.com/s/1o8JYKEe
</code></pre>
<p>谢谢大家！</p>
<h2>讲稿slide可以在<a href="https://github.com/bigwhite/talks/tree/master/osc/2017">这里</a>获取到。</h2>
<p>微博：<a href="http://weibo.com/bigwhite20xx">@tonybai_cn</a><br />
微信公众号：iamtonybai<br />
github.com: https://github.com/bigwhite</p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/10/23/the-speech-script-practice-on-deploying-a-ha-harbor-cluster-for-osc-shenyang-2017/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>基于Harbor和CephFS搭建高可用Private Registry</title>
		<link>https://tonybai.com/2017/06/09/setup-a-high-availability-private-registry-based-on-harbor-and-cephfs/</link>
		<comments>https://tonybai.com/2017/06/09/setup-a-high-availability-private-registry-based-on-harbor-and-cephfs/#comments</comments>
		<pubDate>Fri, 09 Jun 2017 11:27:40 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[aliyun]]></category>
		<category><![CDATA[beego]]></category>
		<category><![CDATA[Ceph]]></category>
		<category><![CDATA[CephFS]]></category>
		<category><![CDATA[distribution]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[docker-compose]]></category>
		<category><![CDATA[ECS]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[HA]]></category>
		<category><![CDATA[harbor]]></category>
		<category><![CDATA[High-Available]]></category>
		<category><![CDATA[innodb]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[mysqldump]]></category>
		<category><![CDATA[redis]]></category>
		<category><![CDATA[registry]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[vmware]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[镜像]]></category>
		<category><![CDATA[镜像仓库]]></category>
		<category><![CDATA[阿里云]]></category>
		<category><![CDATA[高可用]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2333</guid>
		<description><![CDATA[我们有给客户搭建私有容器仓库的需求。开源的私有容器registry可供选择的不多，除了docker官方的distribution之外，比较知名的是VMware China出品的Harbor，我们选择了harbor。 harbor在docker distribution的基础上增加了一些安全、访问控制、管理的功能以满足企业对于镜像仓库的需求。harbor以docker-compose的规范形式组织各个组件，并通过docker-compose工具进行启停。 不过，harbor默认的安装配置是针对single node的，要想做得可靠性高一些，我们需要自己探索一些可行的方案。本文将结合harbor和CephFS搭建一个满足企业高可用性需求的private registry。 一、实验环境 这里用两台阿里云ECS作为harbor的工作节点： node1: 10.47.217.91 node2: 10.28.61.30 两台主机运行的都是Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-58-generic x86_64)，使用root用户。 docker版本与docker-compose的版本如下： # docker version Client: Version: 1.12.5 API version: 1.24 Go version: go1.6.4 Git commit: 7392c3b Built: Fri Dec 16 02:42:17 2016 OS/Arch: linux/amd64 Server: Version: 1.12.5 API version: 1.24 Go version: go1.6.4 Git commit: 7392c3b [...]]]></description>
			<content:encoded><![CDATA[<p>我们有给客户搭建私有容器仓库的需求。开源的私有容器registry可供选择的不多，除了docker官方的<a href="https://github.com/docker/distribution">distribution</a>之外，比较知名的是VMware China出品的<a href="https://github.com/vmware/harbor">Harbor</a>，我们选择了harbor。</p>
<p>harbor在<a href="https://github.com/docker/distribution">docker distribution</a>的基础上增加了一些安全、访问控制、管理的功能以满足企业对于镜像仓库的需求。harbor以<a href="https://github.com/docker/compose">docker-compose</a>的规范形式组织各个组件，并通过docker-compose工具进行启停。</p>
<p>不过，harbor默认的安装配置是针对single node的，要想做得可靠性高一些，我们需要自己探索一些可行的方案。本文将结合harbor和<a href="http://tonybai.com/tag/cephfs">CephFS</a>搭建一个满足企业高可用性需求的private registry。</p>
<h2>一、实验环境</h2>
<p>这里用两台阿里云ECS作为harbor的工作节点：</p>
<pre><code>node1:  10.47.217.91
node2:  10.28.61.30

</code></pre>
<p>两台主机运行的都是<a href="http://tonybai.com/tag/ubuntu">Ubuntu</a> 16.04.1 LTS (GNU/Linux 4.4.0-58-generic x86_64)，使用root用户。</p>
<p><a href="http://tonybai.com/tag/docker">docker版本</a>与docker-compose的版本如下：</p>
<pre><code># docker version
Client:
 Version:      1.12.5
 API version:  1.24
 Go version:   go1.6.4
 Git commit:   7392c3b
 Built:        Fri Dec 16 02:42:17 2016
 OS/Arch:      linux/amd64

Server:
 Version:      1.12.5
 API version:  1.24
 Go version:   go1.6.4
 Git commit:   7392c3b
 Built:        Fri Dec 16 02:42:17 2016
 OS/Arch:      linux/amd64

# docker-compose -v
docker-compose version 1.12.0, build b31ff33
</code></pre>
<p><a href="http://tonybai.com/tag/ceph">ceph</a>版本如下：</p>
<pre><code># ceph -v
ceph version 10.2.7
</code></pre>
<p>ceph的安装和配置可参考<a href="http://tonybai.com/2017/05/08/mount-cephfs-acrossing-nodes-in-kubernetes-cluster/">这里</a>。</p>
<h2>二、方案思路</h2>
<p>首先，从部署上说，我们需要的Private Registry是独立于<a href="http://tonybai.com/2016/12/30/install-kubernetes-on-ubuntu-with-kubeadm/">k8s cluster</a>存在的，即在k8s cluster外部，其存储和管理的镜像供k8s cluster 组件以及运行于k8s cluster上的应用使用。</p>
<p>其次，企业对registry有高可用需求，但我们也要有折中，我们的目标并不是理想的完全高可用，那样投入成本可能有些高。一般企业环境下更注重数据安全。因此首要保证harbor的数据安全，这样即便harbor实例宕掉，保证数据依然不会丢失即可。并且生产环境下registry的使用很难称得上高频，对镜像仓库的性能要求也没那么高。这种情况下，harbor的高可用至少有两种方案：</p>
<ul>
<li>多harbor实例共享后端存储</li>
<li>多harbor实例相互数据同步（通过配置两个harbor相互复制镜像数据）</li>
</ul>
<p>harbor原生支持双实例的镜像数据同步。不过这里我们采用第一种方案：即多harbor实例共享后端存储，因为我们有现成的cephfs供harbor使用。理想的方案示意图如下：</p>
<p><img src="http://tonybai.com/wp-content/uploads/harbor-ha-ideal.png" alt="img{512x368}" /></p>
<ul>
<li>每个安放harbor实例的node都mount cephfs；</li>
<li>每个node上的harbor实例（包含组件：ui、db、registry等）都volume mount node上的cephfs mount路径；</li>
<li>通过Load Balance将request流量负载到各个harbor实例上。</li>
</ul>
<p>但这样做可行么？如果这么做，Harbor实例里的mysql container就会“抱怨”：</p>
<pre><code>May 17 22:45:45 172.19.0.1 mysql[12110]: 2017-05-17 14:45:45 1 [ERROR] InnoDB: Unable to lock ./ibdata1, error: 11
May 17 22:45:45 172.19.0.1 mysql[12110]: 2017-05-17 14:45:45 1 [Note] InnoDB: Check that you do not already have another mysqld process using the same InnoDB data or log files.
</code></pre>
<p>MySQL多个实例无法共享一份mysql数据文件。</p>
<p>那么，我们会考虑将harbor连接的mysql放到外面来，使用external database；同时考虑到session共享，我们还需要增加一个存储session信息的redis cluster，这样一来，方案示意图变更如下：</p>
<p><img src="http://tonybai.com/wp-content/uploads/harbor-ha-we-use.png" alt="img{512x368}" /></p>
<p>图中的mysql、redis你即可以用cluster，也可以用单点，还是看你的需求和投入。如果你具备现成的mysql cluster和redis cluster，那么直接用就好了。但是如果你没有，并且你还不想投入这么多(尤其是搞mysql cluster)，那么用单点就好了。考虑到数据安全，可以将单点mysql的数据存储在cephfs上，如果你已经有了现成的cephfs。</p>
<h2>三、在一个node上安装Harbor</h2>
<h3>1、初装步骤</h3>
<p>以一个node上的Harbor安装为例，harbor提供了详细的<a href="https://github.com/vmware/harbor/blob/master/docs/installation_guide.md">安装步骤文档</a>，我们按照步骤逐步进行即可(这里我使用的是1.1.0版本，截至目前为止的最新稳定版本为1.1.1版本)：</p>
<pre><code>~/harbor-install# wget -c https://github.com/vmware/harbor/releases/download/v1.1.0/harbor-offline-installer-v1.1.0.tgz

~/harbor-install# tar zxvf harbor-offline-installer-v1.1.0.tgz

~/harbor-install/harbor# ls -F
common/  docker-compose.notary.yml  docker-compose.yml  harbor.cfg  harbor.v1.1.0.tar.gz  install.sh*  LICENSE  NOTICE  prepare*

~/harbor-install/harbor./install.sh

[Step 0]: checking installation environment ...

Note: docker version: 1.12.5
Note: docker-compose version: 1.12.0
[Step 1]: loading Harbor images ...
... ...
[Step 2]: preparing environment ...
Generated and saved secret to file: /data/secretkey
Generated configuration file: ./common/config/nginx/nginx.conf
Generated configuration file: ./common/config/adminserver/env
Generated configuration file: ./common/config/ui/env
Generated configuration file: ./common/config/registry/config.yml
Generated configuration file: ./common/config/db/env
Generated configuration file: ./common/config/jobservice/env
Generated configuration file: ./common/config/jobservice/app.conf
Generated configuration file: ./common/config/ui/app.conf
Generated certificate, key file: ./common/config/ui/private_key.pem, cert file: ./common/config/registry/root.crt
The configuration files are ready, please use docker-compose to start the service.

[Step 3]: checking existing instance of Harbor ...
[Step 4]: starting Harbor ...

Creating network "harbor_harbor" with the default driver
Creating harbor-log
Creating harbor-db
Creating registry
Creating harbor-adminserver
Creating harbor-ui
Creating nginx
Creating harbor-jobservice

ERROR: for proxy  Cannot start service proxy: driver failed programming external connectivity on endpoint nginx (fdeb3e538d5f8d714ea5c79a9f3f127f05f7ba5d519e09c4c30ef81f40b2fe77): Error starting userland proxy: listen tcp 0.0.0.0:80: bind: address already in use
</code></pre>
<p>harbor实例默认的监听端口是80，但一般node上的80口都会被占用，因此我们需要修改一个端口号。注意：此时harbor仅启动成功了一些container而已，尚无法正常工作。</p>
<h3>2、修改harbor proxy组件的listen端口</h3>
<p>harbor的proxy组件就是一个nginx，通过nginx这个反向代理，将不同的服务请求分发到内部其他组件中去。nginx默认监听node的80端口，我们用8060端口替代80端口需要进行两处配置修改：</p>
<pre><code>1、harbor.cfg

hostname = node_public_ip:8060

2、docker-compose.yml

proxy:
    image: vmware/nginx:1.11.5-patched
    container_name: nginx
    restart: always
    volumes:
      - ./common/config/nginx:/etc/nginx:z
    networks:
      - harbor
    ports:
      - 8060:80   &lt;--- 修改端口映射
      - 443:443
      - 4443:4443
</code></pre>
<p>由于我们修改了harbor.cfg文件，我们需要重新prepare一下，执行下面命令：</p>
<pre><code># docker-compose down -v
Stopping harbor-jobservice ... done
Stopping nginx ... done
Stopping harbor-ui ... done
Stopping harbor-db ... done
Stopping registry ... done
Stopping harbor-adminserver ... done
Stopping harbor-log ... done
Removing harbor-jobservice ... done
Removing nginx ... done
Removing harbor-ui ... done
Removing harbor-db ... done
Removing registry ... done
Removing harbor-adminserver ... done
Removing harbor-log ... done
Removing network harbor_harbor

# ./prepare
Clearing the configuration file: ./common/config/nginx/nginx.conf
Clearing the configuration file: ./common/config/ui/env
Clearing the configuration file: ./common/config/ui/app.conf
Clearing the configuration file: ./common/config/ui/private_key.pem
Clearing the configuration file: ./common/config/adminserver/env
Clearing the configuration file: ./common/config/jobservice/env
Clearing the configuration file: ./common/config/jobservice/app.conf
Clearing the configuration file: ./common/config/db/env
Clearing the configuration file: ./common/config/registry/config.yml
Clearing the configuration file: ./common/config/registry/root.crt
loaded secret from file: /mnt/cephfs/harbor/data/secretkey
Generated configuration file: ./common/config/nginx/nginx.conf
Generated configuration file: ./common/config/adminserver/env
Generated configuration file: ./common/config/ui/env
Generated configuration file: ./common/config/registry/config.yml
Generated configuration file: ./common/config/db/env
Generated configuration file: ./common/config/jobservice/env
Generated configuration file: ./common/config/jobservice/app.conf
Generated configuration file: ./common/config/ui/app.conf
Generated certificate, key file: ./common/config/ui/private_key.pem, cert file: ./common/config/registry/root.crt
The configuration files are ready, please use docker-compose to start the service.

# docker-compose up -d

Creating network "harbor_harbor" with the default driver
Creating harbor-log
Creating harbor-adminserver
Creating registry
Creating harbor-db
Creating harbor-ui
Creating harbor-jobservice
Creating nginx
</code></pre>
<p>我们可以通过docker-compose ps命令查看harbor组件的状态：</p>
<pre><code># docker-compose ps
       Name                     Command               State                                 Ports
--------------------------------------------------------------------------------------------------------------------------------
harbor-adminserver   /harbor/harbor_adminserver       Up
harbor-db            docker-entrypoint.sh mysqld      Up      3306/tcp
harbor-jobservice    /harbor/harbor_jobservice        Up
harbor-log           /bin/sh -c crond &amp;&amp; rm -f  ...   Up      127.0.0.1:1514-&gt;514/tcp
harbor-ui            /harbor/harbor_ui                Up
nginx                nginx -g daemon off;             Up      0.0.0.0:443-&gt;443/tcp, 0.0.0.0:4443-&gt;4443/tcp, 0.0.0.0:8060-&gt;80/tcp
registry             /entrypoint.sh serve /etc/ ...   Up      5000/tcp
</code></pre>
<p>如果安全组将8060端口打开，通过访问:http://node_public_ip:8060，你将看到如下harbor的web页面：</p>
<p><img src="http://tonybai.com/wp-content/uploads/harbor-homepage.png" alt="img{512x368}" /></p>
<p>我们可以通过harbor内置的默认用户名和密码admin/Harbor12345登录harbor ui。当然，我们更重要的是通过cmdline访问harbor，push和pull image。如果这时你直接尝试docker login harbor_url，你可能会得到如下错误日志：</p>
<pre><code># docker login -u admin -p Harbor12345 node_public_ip:8060
Error response from daemon: Get https://node_public_ip:8060/v1/users/: http: server gave HTTP response to HTTPS client
</code></pre>
<p>这是因为docker默认采用https访问registry，因此我们需要在docker engine的配置中，添加&#8211;insecure-registry option。关于ubuntu 16.04下docker配置的问题，请参考<a href="http://tonybai.com/2016/12/27/when-docker-meets-systemd/">这里</a>：</p>
<pre><code>DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4 --registry-mirror=https://xxxxx.mirror.aliyuncs.com --insecure-registry=node_public_ip:8060"
</code></pre>
<p>重启docker engine后尝试再次登录harbor：</p>
<pre><code>docker login -u admin -p Harbor12345 node_public_ip:8060
Login Succeeded
</code></pre>
<p>一旦docker client login ok，我们就可以通过docker client对harbor中的相关repository进行操作了。</p>
<h2>四、挂载路径修改</h2>
<p>默认情况下，harbor将数据volume挂载到主机的/data路径下面。但由于我们采用ceph共享存储保证数据的高可用，需要修改harbor组件内容器的挂载路径，将其mount到共享存储挂载node上的路径：/mnt/cephfs/harbor/data/。对比两个路径，可以看出前缀由”/”变为了”/mnt/cephfs/harbor/”，我们需要修改docker-compose.yml和harbor.cfg两个文件。</p>
<p>由于docker-compose.yml文件较长，这里将原始文件改名为docker-compose.yml.orig，并将其与修改后的docker-compose.yml做对比：</p>
<pre><code># diff  docker-compose.yml.orig docker-compose.yml
8c8
&lt;       - /var/log/harbor/:/var/log/docker/:z
---
&gt;       - /mnt/cephfs/harbor/log/:/var/log/docker/:z
20c20
&lt;       - /data/registry:/storage:z
---
&gt;       - /mnt/cephfs/harbor/data/registry:/storage:z
40c40
&lt;       - /data/database:/var/lib/mysql:z
---
&gt;       - /mnt/cephfs/harbor/data/database:/var/lib/mysql:z
59,61c59,61
&lt;       - /data/config/:/etc/adminserver/config/:z
&lt;       - /data/secretkey:/etc/adminserver/key:z
&lt;       - /data/:/data/:z
---
&gt;       - /mnt/cephfs/harbor/data/config/:/etc/adminserver/config/:z
&gt;       - /mnt/cephfs/harbor/data/secretkey:/etc/adminserver/key:z
&gt;       - /mnt/cephfs/harbor/data/:/data/:z
80,81c80,81
&lt;       - /data/secretkey:/etc/ui/key:z
&lt;       - /data/ca_download/:/etc/ui/ca/:z
---
&gt;       - /mnt/cephfs/harbor/data/secretkey:/etc/ui/key:z
&gt;       - /mnt/cephfs/harbor/data/ca_download/:/etc/ui/ca/:z
100c100
&lt;       - /data/job_logs:/var/log/jobs:z
---
&gt;       - /mnt/cephfs/harbor/data/job_logs:/var/log/jobs:z
102c102
&lt;       - /data/secretkey:/etc/jobservice/key:z
---
&gt;       - /mnt/cephfs/harbor/data/secretkey:/etc/jobservice/key:z

</code></pre>
<p>harbor.cfg文件需要修改的地方不多：</p>
<pre><code>// harbor.cfg

#The path of cert and key files for nginx, they are applied only the protocol is set to https
ssl_cert = /mnt/cephfs/harbor/data/cert/server.crt
ssl_cert_key = /mnt/cephfs/harbor/data/cert/server.key

#The path of secretkey storage
secretkey_path = /mnt/cephfs/harbor/data
</code></pre>
<p>配置修改完毕后，执行如下命令：</p>
<pre><code># docker-compose down -v
# prepare
# docker-compose up -d
</code></pre>
<p>新的harbor实例就启动起来了。注意：这一步我们用cephfs替换了本地存储，主要的存储变动针对log、database和registry三个输出数据的组件。你也许会感受到cephfs给harbor ui页面加载带来的影响，实感要比之前的加载慢一些。</p>
<h2>五、使用外部数据库(external database)</h2>
<p>前面提到了挂载ceph后，多个node上harbor实例中的db组件将出现竞争问题，导致只有一个node上的harbor db组件可以工作。因此，我们要使用外部数据库(或db集群)来解决这个问题。但是harbor官方针对如何配置使用外部DB很是“讳莫如深”，我们只能自己探索。</p>
<p>假设我们已经有了一个external database，并且建立了harbor这个user，并做了相应的授权。由于harbor习惯了独享database，在测试环境下可以考虑</p>
<pre><code>GRANT ALL ON *.* TO 'harbor'@'%';
</code></pre>
<h3>1、迁移数据</h3>
<p>如果此时镜像库中已经有了数据，我们需要做一些迁移工作。</p>
<p>attach到harbor db组件的container中，将registry这张表dump到registry.dump文件中：</p>
<pre><code>#docker exec -i -t  6e1e4b576315  bash

在db container中：
# mysqldump -u root -p --databases registry &gt; registry.dump

回到node，将dump文件从container中copy出来：

#docker cp 6e1e4b576315:/root/registry.dump ./

再mysql login到external Database，将registry.dump文件导入：

# mysql -h external_db_ip -P 3306 -u harbor -p
# mysql&gt; source ./registry.dump;

</code></pre>
<h3>2、修改harbor配置，使得ui、jobservice组件连接external db</h3>
<p>根据当前<a href="https://github.com/vmware/harbor/wiki/Architecture-Overview-of-Harbor">harbor architecture</a>图所示：</p>
<p><img src="http://tonybai.com/wp-content/uploads/harbor-arch.png" alt="img{512x368}" /></p>
<p>与database“有染”的组件包括ui和jobservice，如何通过配置修改来让这两个组件放弃老db，访问新的external db呢？这要从挖掘配置开始。harbor的组件配置都在common/config下：</p>
<pre><code>~/harbor-install/harbor# tree -L 3 common
common
├── config
│   ├── adminserver
│   │   └── env
│   ├── db
│   │   └── env
│   ├── jobservice
│   │   ├── app.conf
│   │   └── env
│   ├── nginx
│   │   └── nginx.conf
│   ├── registry
│   │   ├── config.yml
│   │   └── root.crt
│   └── ui
│       ├── app.conf
│       ├── env
│       └── private_key.pem
└── templates
 ... ...
</code></pre>
<p>在修改config之前，我们先docker-compose down掉harbor。接下来，我们看到ui和jobservice下都有env文件，这里想必就是可以注入新db的相关访问信息的地方，我们来试试！</p>
<pre><code>// common/config/ui/env
LOG_LEVEL=debug
CONFIG_PATH=/etc/ui/app.conf
UI_SECRET=$ui_secret
JOBSERVICE_SECRET=$jobservice_secret
GODEBUG=netdns=cgo
MYSQL_HOST=new_db_ip
MYSQL_PORT=3306
MYSQL_USR=harbor
MYSQL_PWD=harbor_password

// common/config/jobservice/env
LOG_LEVEL=debug
CONFIG_PATH=/etc/jobservice/app.conf
UI_SECRET=$ui_secret
JOBSERVICE_SECRET=$jobservice_secret
GODEBUG=netdns=cgo
MYSQL_HOST=new_db_ip
MYSQL_PORT=3306
MYSQL_USR=harbor
MYSQL_PWD=harbor_password
</code></pre>
<p>同时，由于不再需要harbor_db组件，<em>因此切记：要将其从docker-compose.yml中剔除！</em>。docker-compose up -d重新创建harbor各组件容器并启动！Harbor的日志可以在挂载的ceph路径： /mnt/cephfs/harbor/log下查找到：</p>
<pre><code>/mnt/cephfs/harbor/log# tree 2017-06-09
2017-06-09
├── adminserver.log
├── anacron.log
├── CROND.log
├── jobservice.log
├── mysql.log
├── proxy.log
├── registry.log
├── run-parts.log
└── ui.log
</code></pre>
<p>我们以ui.log为例，我们发现harbor启动后，ui.log输出如下错误日志(jobservice.log也是相同)：</p>
<pre><code>Jun  9 11:00:17 172.19.0.1 ui[16039]: 2017-06-09T03:00:17Z [INFO] initializing database: type-MySQL host-mysql port-3306 user-root database-registry
Jun  9 11:00:18 172.19.0.1 ui[16039]: 2017-06-09T03:00:18Z [ERROR] [utils.go:94]: failed to connect to tcp://mysql:3306, retry after 2 seconds :dial tcp: lookup mysql: no such host
</code></pre>
<p>我们明明注入了新的db env，为何ui还是要访问“tcp://mysql:3306”呢？我们docker inspect一下ui的container，看看env是否包含我们添加的那些：</p>
<pre><code># docker inspect e91ab20e1dcb
... ...
            "Env": [
                "DATABASE_TYPE=mysql",
                "MYSQL_HOST=database_ip",
                "MYSQL_PORT=3306",
                "MYSQL_PWD=harbor_password",
                "MYSQL_USR=harbor",
                "MYSQL_DATABASE=registry",
            ],
.... ...
</code></pre>
<p>env已经注入，那么为何ui、jobservice无法连接到external database呢？要想搞清楚这点，我们只能去<em>“啃代码”</em>了。还好harbor代码并非很难啃。我们发现基于beego实现的ui、jobservice两个组件并未直接通过os.Getenv去获取这些env变量，而是调用了adminserver组件的服务。adminserver在初始化时，在RESET环境变量为true的情况下，读取了common/config/adminserver/env下的所有环境变量。</p>
<p>搞清楚原理后，我们知道了要修改的是common/config/adminserver/env，而不是common/config/ui/env和common/config/jobservice/env。我们将后两个文件还原。修改common/config/adminserver/env文件：</p>
<pre><code>//common/config/adminserver/env
... ...
MYSQL_HOST=new_db_ip
MYSQL_PORT=3306
MYSQL_USR=harbor
MYSQL_PWD=harbor_password
... ...
RESET=true    &lt;--- 改为true，非常关键

</code></pre>
<p>重新up harbor服务后，我们发现ui, jobservice与新database的连接成功了！打开harbor web页面，登录进去，我们看到了之前已经添加的用户、项目和镜像文件。</p>
<h3>3、一劳永逸</h3>
<p>如果你重新执行prepare，那么上面对config目录下的配置修改将被重新覆盖。如果要一劳永逸，那么需要修改的是common/templates下面的同位置同名配置文件。</p>
<h2>六、安装其他节点上的harbor实例</h2>
<p>前面，我们只搭建了一个节点，为的是验证方案的可行性。要实现高可用，我们还需要在其他节点上安装harbor实例。由于多个节点上harbor实例共同挂载ceph的同一目录，因此考虑到log的分离，在部署其他节点上的harbor时，最好对docker-compose.yml下log组件的volumes映射路径进行调整，以在多个节点间做隔离，便于日志查看，比如：</p>
<pre><code>volumes:
      - /mnt/cephfs/harbor/log1/:/var/log/docker/:z
</code></pre>
<p>除此之外，各个节点上的harbor配置与上述配置完全一致。</p>
<h2>七、共享session设置</h2>
<p>到harbor的请求被负载均衡分发到多个node上的harbor实例上，这样就有了session共享的需求。Harbor对此已经给予了支持。在ui组件的代码中，我们发现ui在初始化时使用Getenv获取”_REDIS_URL”这个环境变量的值，因此我们只需要将_REDIS_URL这个环境变量配置到各个节点harbor ui组件的env文件中即可：</p>
<pre><code>// common/config/adminserver/env

LOG_LEVEL=debug
CONFIG_PATH=/etc/ui/app.conf
UI_SECRET=LuAwkKUtYjF4l0mQ
JOBSERVICE_SECRET=SmsO1kVo4SrmgOIp
GODEBUG=netdns=cgo
_REDIS_URL=redis_ip:6379,100,redis_password,0
</code></pre>
<p>重新up harbor后，session共享生效。</p>
<p>不过光有一个外部redis存储共享session还不够，请求在多个harbor实例中的registry组件中进行鉴权需要harbor各个实例share相同的<a href="https://github.com/vmware/harbor/blob/master/docs/customize_token_service.md">key和certificate</a>。好在，我们的多harbor实例通过ceph共享存储，key和cert本就是共享的，都存放在目录：/mnt/cephfs/harbor/data/cert/的下边，因此也就不需要在各个harbor实例间同步key和cert了。</p>
<h2>八、更换为域名访问</h2>
<p>我们有通过域名访问docker registry的需求，那么直接通过域名访问harbor ui和registry是否可行呢？这要看harbor  nginx的配置:</p>
<pre><code># docker ps |grep nginx
fa92765e8871        vmware/nginx:1.11.5-patched   "nginx -g 'daemon off"   3 hours ago
Up 3 hours          0.0.0.0:443-&gt;443/tcp, 0.0.0.0:4443-&gt;4443/tcp, 0.0.0.0:8060-&gt;80/tcp               nginx

# docker exec fa92765e8871 cat /etc/nginx/nginx.conf

... ...
http {
   server {
    listen 80;
   ... ...

}

</code></pre>
<p>nginx在http server block并未对域名或ip进行匹配，因此直接将域名A地址设置为反向代理的地址或直接解析为Harbor暴露的公网ip地址都是可以正常访问harbor服务的，当然也包括image push和pull服务。</p>
<blockquote>
<p>注意：如果使用域名访问harbor服务，那么就将harbor.cfg中的hostname赋值为你的”域名+端口”，并重新prepare。否则你可能会发现通过harbor域名上传的image无法pull，因为其pull的地址为由ip组成的地址，以docker push hub.tonybai.com:8989/myrepo/foo:latest为例，push成功后，docker pull hub.tonybai.com:8989/myrepo/foo:latest可能提示你找不到该image，因为harbor中该imag<br />
  e的地址可能是my_ip_address:8989/myrepo/foo:latest。</p>
</blockquote>
<h2>九、统一registry的证书和token service的私钥</h2>
<p>这是在本篇文章发表之后发现的问题，针对该问题，我专门写了一篇文章：《<a href="http://tonybai.com/2017/06/15/fix-auth-fail-when-login-harbor-registry/">解决登录Harbor Registry时鉴权失败的问题</a>》,请移步这篇文章，完成HA Harbor的搭建。</p>
<h2>十、参考资料</h2>
<ul>
<li><a href="https://github.com/vmware/harbor/blob/master/docs/installation_guide.md">Installation &amp; Configuration Guide</a></li>
<li><a href="http://dockone.io/article/1640">用Harbor实现容器镜像仓库的管理和运维</a></li>
</ul>
<hr />
<p>微博：<a href="http://weibo.com/bigwhite20xx">@tonybai_cn</a><br />
微信公众号：iamtonybai<br />
github.com: https://github.com/bigwhite</p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/06/09/setup-a-high-availability-private-registry-based-on-harbor-and-cephfs/feed/</wfw:commentRss>
		<slash:comments>20</slash:comments>
		</item>
		<item>
		<title>Kubernetes集群node主机名修改导致的异常</title>
		<link>https://tonybai.com/2017/05/09/exception-caused-by-kubernetes-node-hostname-change/</link>
		<comments>https://tonybai.com/2017/05/09/exception-caused-by-kubernetes-node-hostname-change/#comments</comments>
		<pubDate>Tue, 09 May 2017 02:37:23 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Ceph]]></category>
		<category><![CDATA[ceph-deploy]]></category>
		<category><![CDATA[CephFS]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[hostname]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[kubelet]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Master]]></category>
		<category><![CDATA[minion]]></category>
		<category><![CDATA[集群]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2309</guid>
		<description><![CDATA[除了在生产环境使用的Kubernetes 1.3.7集群之外，我这里还有一套1.5.1的Kubernetes测试环境，这个测试环境一来用于验证各种技术方案，二来也是为了跟踪Kubernetes的最新进展。本篇要记录的一个异常就是发生在该测试Kubernetes集群中的。 一、缘起 前两天我在Kubernetes测试环境搭建一套Ceph，为了便于ceph-deploy的安装，我通过hostnamectl命令将阿里云默认提供的复杂又冗长的主机名改为短小且更有意义的主机名： iZ25beglnhtZ -&#62; yypdmaster iz2ze39jeyizepdxhwqci6z -&#62; yypdnode 以yypdmaster为例，修改过程如下： # hostnamectl --static set-hostname yypdmaster # hostnamectl status Static hostname: yypdmaster Transient hostname: iZ25beglnhtZ Icon name: computer-vm Chassis: vm Machine ID: 91aa4b8f2556de49e743dc2f53e8a5c4 Boot ID: 5d0e642ebafa460086388da4177e488e Virtualization: kvm Operating System: Ubuntu 16.04.1 LTS Kernel: Linux 4.4.0-58-generic Architecture: x86-64 # cat /etc/hostname yypdmaster hostnamectl并未修改/etc/hosts，我手动在/etc/hosts中将yypdmaster对应的ip配置上： xx.xx.xx.xx yypdmaster [...]]]></description>
			<content:encoded><![CDATA[<p>除了在生产环境使用的<a href="http://tonybai.com/2016/10/18/learn-how-to-install-kubernetes-on-ubuntu/">Kubernetes 1.3.7集群</a>之外，我这里还有<a href="http://tonybai.com/2016/12/30/install-kubernetes-on-ubuntu-with-kubeadm/">一套1.5.1的Kubernetes测试环境</a>，这个测试环境一来用于验证各种技术方案，二来也是为了跟踪Kubernetes的最新进展。本篇要记录的一个异常就是发生在该测试Kubernetes集群中的。</p>
<h3>一、缘起</h3>
<p>前两天我在Kubernetes测试环境搭建一套<a href="http://tonybai.com/2016/11/07/integrate-kubernetes-with-ceph-rbd/">Ceph</a>，为了便于ceph-deploy的安装，我通过hostnamectl命令将阿里云默认提供的复杂又冗长的主机名改为短小且更有意义的主机名：</p>
<pre><code>iZ25beglnhtZ -&gt; yypdmaster
iz2ze39jeyizepdxhwqci6z -&gt; yypdnode

以yypdmaster为例，修改过程如下：

# hostnamectl --static set-hostname yypdmaster
# hostnamectl status
Static hostname: yypdmaster
Transient hostname: iZ25beglnhtZ
         Icon name: computer-vm
           Chassis: vm
        Machine ID: 91aa4b8f2556de49e743dc2f53e8a5c4
           Boot ID: 5d0e642ebafa460086388da4177e488e
    Virtualization: kvm
  Operating System: Ubuntu 16.04.1 LTS
            Kernel: Linux 4.4.0-58-generic
      Architecture: x86-64

# cat /etc/hostname
yypdmaster

hostnamectl并未修改/etc/hosts，我手动在/etc/hosts中将yypdmaster对应的ip配置上：

xx.xx.xx.xx yypdmaster
</code></pre>
<p>重新登录后，我们看到主机名状态：Transient hostname不见了，只剩下了静态主机名：</p>
<pre><code># hostnamectl status
   Static hostname: yypdmaster
         Icon name: computer-vm
           Chassis: vm
        Machine ID: 91aa4b8f2556de49e743dc2f53e8a5c4
           Boot ID: 5d0e642ebafa460086388da4177e488e
    Virtualization: kvm
  Operating System: Ubuntu 16.04.1 LTS
            Kernel: Linux 4.4.0-58-generic
      Architecture: x86-64

</code></pre>
<p>另外一台主机也是如此修改。主机名修改后，整个k8s集群工作一切正常，因此我最初以为hostname的修改对k8s cluster的运行没有影响。</p>
<h3>二、集群”Crash”</h3>
<p>昨天在做<a href="http://tonybai.com/2017/05/08/mount-cephfs-acrossing-nodes-in-kubernetes-cluster/">跨节点挂载Cephfs</a>测试时，发现在yypdmaster上kubectl exec另外一个node上的pod不好用，提示：连接10250端口超时！而且从错误日志来看，yypdmaster上的k8s组件居然通过yypdnode的外网ip去访问yypdnode上的10250端口，也就是yypdnode上kubelet监听的端口。由于aliyun的安全组规则限制，这个端口是不允许外网访问的，因此timeout错误是合理的。但为什么之前集群都是好好的？突然间出现这个问题呢？为什么不用内网的ip地址访问呢？</p>
<p>我尝试重启了yypdnode上的kubelet服务。不过似乎没什么效果！正当我疑惑时，我发现集群似乎”Crash”了，下面是当时查看集群的pod情况的输出：</p>
<pre><code># kubectl get pod --all-namespaces -o wide

NAMESPACE                    NAME                                    READY     STATUS             RESTARTS   AGE       IP             NODE
default                      ceph-pod2                               1/1       Unknown            0          26m       172.30.192.4   iz2ze39jeyizepdxhwqci6z
default                      ceph-pod2-with-secret                   1/1       Unknown            0          38m       172.30.192.2   iz2ze39jeyizepdxhwqci6z
default                      ceph-pod2-with-secret-on-master         1/1       Unknown            0          34m       172.30.0.51    iz25beglnhtz
default                      nginx-kit-3630450072-2c0jk              0/2       Pending            0          12m       &lt;none&gt;
default                      nginx-kit-3630450072-3n50m              2/2       Unknown            20         35d       172.30.0.44    iz25beglnhtz
default                      nginx-kit-3630450072-90v4q              0/2       Pending            0          12m       &lt;none&gt;
default                      nginx-kit-3630450072-j8qrk              2/2       Unknown            20         72d       172.30.0.47    iz25beglnhtz
kube-system                  dummy-2088944543-9382n                  1/1       Running            0          12m       xx.xx.xx.xx   yypdmaster
kube-system                  dummy-2088944543-93f4c                  1/1       Unknown            16         130d      xx.xx.xx.xx   iz25beglnhtz
kube-system                  elasticsearch-logging-v1-dhl35          1/1       Running            0          12m       172.30.192.6   yypdnode
kube-system                  elasticsearch-logging-v1-s3sbj          1/1       Unknown            9          35d       172.30.0.45    iz25beglnhtz
kube-system                  elasticsearch-logging-v1-t8wg0          1/1       Unknown            29         68d       172.30.0.43    iz25beglnhtz
kube-system                  elasticsearch-logging-v1-zdp19          1/1       Running            0          12m       172.30.0.3     yypdmaster
kube-system                  etcd-iz25beglnhtz                       1/1       Unknown            17         130d      xx.xx.xx.xx   iz25beglnhtz
kube-system                  etcd-yypdmaster                         1/1       Running            17         17m       xx.xx.xx.xx   yypdmaster
kube-system                  fluentd-es-v1.22-ggvv4                  1/1       NodeLost           24         68d       172.30.0.46    iz25beglnhtz
kube-system                  fluentd-es-v1.22-rj871                  1/1       Running            0          17m       172.30.0.1     yypdmaster
kube-system                  fluentd-es-v1.22-xn77x                  1/1       NodeLost           0          6d        172.30.192.0   iz2ze39jeyizepdxhwqci6z
kube-system                  fluentd-es-v1.22-z82rz                  1/1       Running            0          18m       172.30.192.5   yypdnode
kube-system                  kibana-logging-3746979809-dplzv         1/1       Running            0          12m       172.30.0.4     yypdmaster
kube-system                  kibana-logging-3746979809-lq9m3         1/1       Unknown            9          35d       172.30.0.49    iz25beglnhtz
kube-system                  kube-apiserver-iz25beglnhtz             1/1       Unknown            19         104d      xx.xx.xx.xx   iz25beglnhtz
kube-system                  kube-apiserver-yypdmaster               1/1       Running            19         17m       xx.xx.xx.xx   yypdmaster
kube-system                  kube-controller-manager-iz25beglnhtz    1/1       Unknown            21         130d      xx.xx.xx.xx   iz25beglnhtz
kube-system                  kube-controller-manager-yypdmaster      1/1       Running            21         17m       xx.xx.xx.xx   yypdmaster
kube-system                  kube-discovery-1769846148-wh1z4         1/1       Unknown            12         73d       xx.xx.xx.xx   iz25beglnhtz
kube-system                  kube-discovery-1769846148-z2v87         0/1       Pending            0          12m       &lt;none&gt;
kube-system                  kube-dns-2924299975-206tg               4/4       Unknown            129        130d      172.30.0.48    iz25beglnhtz
kube-system                  kube-dns-2924299975-g1kks               4/4       Running            0          12m       172.30.0.5     yypdmaster
kube-system                  kube-proxy-3z29k                        1/1       Running            0          18m       yy.yy.yy.yy    yypdnode
kube-system                  kube-proxy-kfzxv                        1/1       Running            0          17m       xx.xx.xx.xx   yypdmaster
kube-system                  kube-proxy-n2xmf                        1/1       NodeLost           16         130d      xx.xx.xx.xx   iz25beglnhtz

</code></pre>
<p>观察这个输出，我们看到几点异常：</p>
<ul>
<li>不常见的Pod状态：Unknown、NodeLost</li>
<li>Node一列居然出现了四个Node: yypdmaster、yypdnode、 iz25beglnhtz和  iz2ze39jeyizepdxhwqci6z</li>
</ul>
<p>等了一会儿，这种状态依然不见好转。我于是重启了master上的kubelet、重启了两个节点上的docker engine，不过启动后问题依旧！</p>
<p>查看Running状态的Pod情况：</p>
<pre><code># kubectl get pod --all-namespaces -o wide|grep Running
kube-system                  dummy-2088944543-9382n                  1/1       Running            0          18m       xx.xx.xx.xx   yypdmaster
kube-system                  elasticsearch-logging-v1-dhl35          1/1       Running            0          18m       172.30.192.6   yypdnode
kube-system                  elasticsearch-logging-v1-zdp19          1/1       Running            0          18m       172.30.0.3     yypdmaster
kube-system                  etcd-yypdmaster                         1/1       Running            17         23m       xx.xx.xx.xx   yypdmaster
kube-system                  fluentd-es-v1.22-rj871                  1/1       Running            0          23m       172.30.0.1     yypdmaster
kube-system                  fluentd-es-v1.22-z82rz                  1/1       Running            0          24m       172.30.192.5   yypdnode
kube-system                  kibana-logging-3746979809-dplzv         1/1       Running            0          18m       172.30.0.4     yypdmaster
kube-system                  kube-apiserver-yypdmaster               1/1       Running            19         23m       xx.xx.xx.xx   yypdmaster
kube-system                  kube-controller-manager-yypdmaster      1/1       Running            21         23m       xx.xx.xx.xx   yypdmaster
kube-system                  kube-dns-2924299975-g1kks               4/4       Running            0          18m       172.30.0.5     yypdmaster
kube-system                  kube-proxy-3z29k                        1/1       Running            0          24m       yy.yy.yy.yy    yypdnode
kube-system                  kube-proxy-kfzxv                        1/1       Running            0          23m       xx.xx.xx.xx   yypdmaster
kube-system                  kube-scheduler-yypdmaster               1/1       Running            22         23m       xx.xx.xx.xx   yypdmaster
kube-system                  kubernetes-dashboard-3109525988-cj74d   1/1       Running            0          18m       172.30.0.6     yypdmaster
mioss-namespace-s0fcvegcmw   console-sm7cg2-101699315-f3g55          1/1       Running            0          18m       172.30.0.7     yypdmaster
</code></pre>
<p>似乎Kubernetes集群并未真正”Crash”，但从Node列来看，正常的pod归属的node不是yypdmaster就是yypdnode， iz25beglnhtz和  iz2ze39jeyize</p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/05/09/exception-caused-by-kubernetes-node-hostname-change/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Kubernetes集群跨节点挂载CephFS</title>
		<link>https://tonybai.com/2017/05/08/mount-cephfs-acrossing-nodes-in-kubernetes-cluster/</link>
		<comments>https://tonybai.com/2017/05/08/mount-cephfs-acrossing-nodes-in-kubernetes-cluster/#comments</comments>
		<pubDate>Mon, 08 May 2017 09:51:46 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[CehpRBD]]></category>
		<category><![CDATA[Ceph]]></category>
		<category><![CDATA[CephFS]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[persistent-volume]]></category>
		<category><![CDATA[persistent-volume-claim]]></category>
		<category><![CDATA[pod]]></category>
		<category><![CDATA[PV]]></category>
		<category><![CDATA[PVC]]></category>
		<category><![CDATA[rbd]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2303</guid>
		<description><![CDATA[在Kubernetes集群中运行有状态服务或应用总是不那么容易的。比如，之前我在项目中使用了CephRBD，虽然遇到过几次问题，但总体算是运行良好。但最近发现CephRBD无法满足跨节点挂载的需求，我只好另辟蹊径。由于CephFS和CephRBD师出同门，它自然成为了这次我首要考察的目标。这里将跨节点挂载CephFS的考察过程记录一下，一是备忘，二则也可以为其他有相似需求的朋友提供些资料。 一、CephRBD的问题 这里先提一嘴CephRBD的问题。最近项目中有这样的需求：让集群中的Pod共享外部分布式存储，即多个Pod共同挂载一份存储，实现存储共享，这样可大大简化系统设计和复杂性。之前CephRBD都是挂载到一个Pod中运行的，CephRBD是否支持多Pod同时挂载呢？官方文档中给出了否定的答案: 基于CephRBD的Persistent Volume仅支持两种accessmode： ReadWriteOnce和ReadOnlyMany，不支持ReadWriteMany。这样对于有读写需求的Pod来说，一个CephRBD pv仅能被一个node挂载一次。 我们来验证一下这个“不幸的”事实。 我们首先创建一个测试用的image：foo1。这里我利用了项目里写的CephRBD API服务，也可通过ceph命令手工创建： # curl -v -H "Content-type: application/json" -X POST -d '{"kind": "Images","apiVersion": "v1", "metadata": {"name": "foo1", "capacity": 512} ' http://192.168.3.22:8080/api/v1/pools/rbd/images ... ... { "errcode": 0, "errmsg": "ok" } # curl http://192.168.3.22:8080/api/v1/pools/rbd/images { "Kind": "ImagesList", "APIVersion": "v1", "Items": [ { "name": "foo1" } ] } 利用下面文件创建pv和pvc： //ceph-pv.yaml [...]]]></description>
			<content:encoded><![CDATA[<p>在<a href="http://tonybai.com/tag/kubernetes">Kubernetes</a>集群中运行有状态服务或应用总是不那么容易的。比如，之前我在项目中使用了<a href="http://tonybai.com/2016/11/07/integrate-kubernetes-with-ceph-rbd/">CephRBD</a>，虽然<a href="http://tonybai.com/2017/02/17/temp-fix-for-pod-unable-mount-cephrbd-volume/">遇到过几次问题</a>，但总体算是运行良好。但最近发现CephRBD无法满足跨节点挂载的需求，我只好另辟蹊径。由于CephFS和CephRBD师出同门，它自然成为了这次我首要考察的目标。这里将跨节点挂载CephFS的考察过程记录一下，一是备忘，二则也可以为其他有相似需求的朋友提供些资料。</p>
<h2>一、CephRBD的问题</h2>
<p>这里先提一嘴CephRBD的问题。最近项目中有这样的需求：让集群中的Pod共享外部分布式存储，即多个Pod共同挂载一份存储，实现存储共享，这样可大大简化系统设计和复杂性。之前CephRBD都是挂载到一个Pod中运行的，CephRBD是否支持多Pod同时挂载呢？<a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes">官方文档</a>中给出了否定的答案: 基于CephRBD的Persistent Volume仅支持两种accessmode：<br />
ReadWriteOnce和ReadOnlyMany，不支持ReadWriteMany。这样对于有读写需求的Pod来说，一个CephRBD pv仅能被一个node挂载一次。</p>
<p>我们来验证一下这个“不幸的”事实。</p>
<p>我们首先创建一个测试用的image：foo1。这里我利用了项目里写的<a href="http://tonybai.com/2016/11/21/kuberize-ceph-rbd-api-service/">CephRBD API</a>服务，也可通过ceph命令手工创建：</p>
<pre><code># curl -v  -H "Content-type: application/json" -X POST -d '{"kind": "Images","apiVersion": "v1", "metadata": {"name": "foo1", "capacity": 512} ' http://192.168.3.22:8080/api/v1/pools/rbd/images
... ...
{
  "errcode": 0,
  "errmsg": "ok"
}

# curl http://192.168.3.22:8080/api/v1/pools/rbd/images
{
  "Kind": "ImagesList",
  "APIVersion": "v1",
  "Items": [
    {
      "name": "foo1"
    }
  ]
}

</code></pre>
<p>利用下面文件创建pv和pvc：</p>
<pre><code>//ceph-pv.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: foo-pv
spec:
  capacity:
    storage: 512Mi
  accessModes:
    - ReadWriteMany
  rbd:
    monitors:
      - ceph_monitor_ip:port
    pool: rbd
    image: foo1
    user: admin
    secretRef:
      name: ceph-secret
    fsType: ext4
    readOnly: false
  persistentVolumeReclaimPolicy: Recycle

//ceph-pvc.yaml

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: foo-claim
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 512Mi
</code></pre>
<p>创建后：</p>
<pre><code># kubectl get pv
[NAME                CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM                        REASON    AGE
foo-pv              512Mi      RWO           Recycle         Bound     default/foo-claim                      20h

# kubectl get pvc
NAME                 STATUS    VOLUME              CAPACITY   ACCESSMODES   AGE
foo-claim            Bound     foo-pv              512Mi      RWO           20h
</code></pre>
<p>创建挂载上述image的Pod：</p>
<pre><code>// ceph-pod2.yaml

apiVersion: v1
kind: Pod
metadata:
  name: ceph-pod2
spec:
  containers:
  - name: ceph-ubuntu2
    image: ubuntu:14.04
    command: ["tail", "-f", "/var/log/bootstrap.log"]
    volumeMounts:
    - name: ceph-vol2
      mountPath: /mnt/cephrbd/data
      readOnly: false
  volumes:
  - name: ceph-vol2
    persistentVolumeClaim:
      claimName: foo-claim

</code></pre>
<p>创建成功后，我们可以查看挂载目录的数据：</p>
<pre><code># kubectl exec ceph-pod2 ls /mnt/cephrbd/data
1.txt
lost+found
</code></pre>
<p>我们在同一个kubernetes node上再启动一个pod（可以把上面的ceph-pod2.yaml的pod name改为ceph-pod3），挂载同样的pv：</p>
<pre><code>NAMESPACE                    NAME                                    READY     STATUS    RESTARTS   AGE       IP             NODE
default                      ceph-pod2                               1/1       Running   0          3m        172.16.57.9    xx.xx.xx.xx
default                      ceph-pod3                               1/1       Running   0          0s        172.16.57.10    xx.xx.xx.xx
</code></pre>
<pre><code># kubectl exec ceph-pod3 ls /mnt/cephrbd/data
1.txt
lost+found
</code></pre>
<p>我们通过ceph-pod2写一个文件，在ceph-pod3中将其读出：</p>
<pre><code># kubectl exec ceph-pod2 -- bash -c "for i in {1..10}; do sleep 1; echo 'pod2: Hello, World'&gt;&gt; /mnt/cephrbd/data/foo.txt ; done "
root@node1:~/k8stest/k8s-cephrbd/footest# kubectl exec ceph-pod3 cat /mnt/cephrbd/data/foo.txt
pod2: Hello, World
pod2: Hello, World
pod2: Hello, World
pod2: Hello, World
pod2: Hello, World
pod2: Hello, World
pod2: Hello, World
pod2: Hello, World
pod2: Hello, World
pod2: Hello, World

</code></pre>
<p>到目前为止，在一个node上多个Pod是可以以ReadWrite模式挂载同一个CephRBD的。</p>
<p>我们在另外一个节点启动一个试图挂载该pv的Pod，该Pod启动后一直处于pending状态，通过kubectl describe查看其详细信息，可以看到：</p>
<pre><code>Events:
  FirstSeen    LastSeen    Count    From            SubobjectPath    Type        Reason        Message
  ---------    --------    -----    ----            -------------    --------    ------        -------
.. ...
  2m        37s        2    {kubelet yy.yy.yy.yy}            Warning        FailedMount    Unable to mount volumes for pod "ceph-pod2-master_default(a45f62aa-2bc3-11e7-9baa-00163e1625a9)": timeout expired waiting for volumes to attach/mount for pod "ceph-pod2-master"/"default". list of unattached/unmounted volumes=[ceph-vol2]
  2m        37s        2    {kubelet yy.yy.yy.yy}            Warning        FailedSync    Error syncing pod, skipping: timeout expired waiting for volumes to attach/mount for pod "ceph-pod2-master"/"default". list of unattached/unmounted volumes=[ceph-vol2]
</code></pre>
<p>查看kubelet.log中的错误日志：</p>
<pre><code>I0428 11:39:15.737729    1241 reconciler.go:294] MountVolume operation started for volume "kubernetes.io/rbd/a45f62aa-2bc3-11e7-9baa-00163e1625a9-foo-pv" (spec.Name: "foo-pv") to pod "a45f62aa-2bc3-11e7-9baa-00163e1625a9" (UID: "a45f62aa-2bc3-11e7-9baa-00163e1625a9").
I0428 11:39:15.939183    1241 operation_executor.go:768] MountVolume.SetUp succeeded for volume "kubernetes.io/secret/923700ff-12c2-11e7-9baa-00163e1625a9-default-token-40z0x" (spec.Name: "default-token-40z0x") pod "923700ff-12c2-11e7-9baa-00163e1625a9" (UID: "923700ff-12c2-11e7-9baa-00163e1625a9").
E0428 11:39:17.039656    1241 disk_manager.go:56] failed to attach disk
E0428 11:39:17.039722    1241 rbd.go:228] rbd: failed to setup mount /var/lib/kubelet/pods/a45f62aa-2bc3-11e7-9baa-00163e1625a9/volumes/kubernetes.io~rbd/foo-pv rbd: image foo1 is locked by other nodes
E0428 11:39:17.039857    1241 nestedpendingoperations.go:254] Operation for "\"kubernetes.io/rbd/a45f62aa-2bc3-11e7-9baa-00163e1625a9-foo-pv\" (\"a45f62aa-2bc3-11e7-9baa-00163e1625a9\")" failed. No retries permitted until 2017-04-28 11:41:17.039803969 +0800 CST (durationBeforeRetry 2m0s). Error: MountVolume.SetUp failed for volume "kubernetes.io/rbd/a45f62aa-2bc3-11e7-9baa-00163e1625a9-foo-pv" (spec.Name: "foo-pv") pod "a45f62aa-2bc3-11e7-9baa-00163e1625a9" (UID: "a45f62aa-2bc3-11e7-9baa-00163e1625a9") with: rbd: image foo1 is locked by other nodes

</code></pre>
<p>可以看到“rbd: image foo1 is locked by other nodes”的日志。我们用试验证明了目前CephRBD仅能被k8s中的一个node挂载的事实。</p>
<h2>二、Ceph集群安装mds以支持CephFS</h2>
<p>这次我在两个Ubuntu 16.04的vm上新部署了一套Ceph，过程与之前<a href="http://tonybai.com/2016/11/07/integrate-kubernetes-with-ceph-rbd/">第一次部署Ceph</a>时大同小异，这里就不赘述了。要让Ceph支持CephFS，我们需要安装mds组件，有了前面的基础，通过ceph-deploy工具安装mds十分简单：</p>
<pre><code># ceph-deploy mds create yypdmaster yypdnode
[ceph_deploy.conf][DEBUG ] found configuration file at: /root/.cephdeploy.conf
[ceph_deploy.cli][INFO  ] Invoked (1.5.37): /usr/bin/ceph-deploy mds create yypdmaster yypdnode
[ceph_deploy.cli][INFO  ] ceph-deploy options:
[ceph_deploy.cli][INFO  ]  username                      : None
[ceph_deploy.cli][INFO  ]  verbose                       : False
[ceph_deploy.cli][INFO  ]  overwrite_conf                : False
[ceph_deploy.cli][INFO  ]  subcommand                    : create
[ceph_deploy.cli][INFO  ]  quiet                         : False
[ceph_deploy.cli][INFO  ]  cd_conf                       : &lt;ceph_deploy.conf.cephdeploy.Conf instance at 0x7f60fb5e71b8&gt;
[ceph_deploy.cli][INFO  ]  cluster                       : ceph
[ceph_deploy.cli][INFO  ]  func                          : &lt;function mds at 0x7f60fba4e140&gt;
[ceph_deploy.cli][INFO  ]  ceph_conf                     : None
[ceph_deploy.cli][INFO  ]  mds                           : [('yypdmaster', 'yypdmaster'), ('yypdnode', 'yypdnode')]
[ceph_deploy.cli][INFO  ]  default_release               : False
[ceph_deploy.mds][DEBUG ] Deploying mds, cluster ceph hosts yypdmaster:yypdmaster yypdnode:yypdnode
[yypdmaster][DEBUG ] connected to host: yypdmaster
[yypdmaster][DEBUG ] detect platform information from remote host
[yypdmaster][DEBUG ] detect machine type
[ceph_deploy.mds][INFO  ] Distro info: Ubuntu 16.04 xenial
[ceph_deploy.mds][DEBUG ] remote host will use systemd
[ceph_deploy.mds][DEBUG ] deploying mds bootstrap to yypdmaster
[yypdmaster][DEBUG ] write cluster configuration to /etc/ceph/{cluster}.conf
[yypdmaster][DEBUG ] create path if it doesn't exist
[yypdmaster][INFO  ] Running command: ceph --cluster ceph --name client.bootstrap-mds --keyring /var/lib/ceph/bootstrap-mds/ceph.keyring auth get-or-create mds.yypdmaster osd allow rwx mds allow mon allow profile mds -o /var/lib/ceph/mds/ceph-yypdmaster/keyring
[yypdmaster][INFO  ] Running command: systemctl enable ceph-mds@yypdmaster
[yypdmaster][WARNIN] Created symlink from /etc/systemd/system/ceph-mds.target.wants/ceph-mds@yypdmaster.service to /lib/systemd/system/ceph-mds@.service.
[yypdmaster][INFO  ] Running command: systemctl start ceph-mds@yypdmaster
[yypdmaster][INFO  ] Running command: systemctl enable ceph.target
[yypdnode][DEBUG ] connected to host: yypdnode
[yypdnode][DEBUG ] detect platform information from remote host
[yypdnode][DEBUG ] detect machine type
[ceph_deploy.mds][INFO  ] Distro info: Ubuntu 16.04 xenial
[ceph_deploy.mds][DEBUG ] remote host will use systemd
[ceph_deploy.mds][DEBUG ] deploying mds bootstrap to yypdnode
[yypdnode][DEBUG ] write cluster configuration to /etc/ceph/{cluster}.conf
[yypdnode][DEBUG ] create path if it doesn't exist
[yypdnode][INFO  ] Running command: ceph --cluster ceph --name client.bootstrap-mds --keyring /var/lib/ceph/bootstrap-mds/ceph.keyring auth get-or-create mds.yypdnode osd allow rwx mds allow mon allow profile mds -o /var/lib/ceph/mds/ceph-yypdnode/keyring
[yypdnode][INFO  ] Running command: systemctl enable ceph-mds@yypdnode
[yypdnode][WARNIN] Created symlink from /etc/systemd/system/ceph-mds.target.wants/ceph-mds@yypdnode.service to /lib/systemd/system/ceph-mds@.service.
[yypdnode][INFO  ] Running command: systemctl start ceph-mds@yypdnode
[yypdnode][INFO  ] Running command: systemctl enable ceph.target
</code></pre>
<p>非常顺利。安装后，可以在任意一个node上看到mds在运行：</p>
<pre><code># ps -ef|grep ceph
ceph      7967     1  0 17:23 ?        00:00:00 /usr/bin/ceph-osd -f --cluster ceph --id 1 --setuser ceph --setgroup ceph
ceph     15674     1  0 17:32 ?        00:00:00 /usr/bin/ceph-mon -f --cluster ceph --id yypdnode --setuser ceph --setgroup ceph
ceph     18019     1  0 17:35 ?        00:00:00 /usr/bin/ceph-mds -f --cluster ceph --id yypdnode --setuser ceph --setgroup ceph
</code></pre>
<p>mds是存储cephfs的元信息的，我的ceph是10.2.7版本：</p>
<pre><code># ceph -v
ceph version 10.2.7 (50e863e0f4bc8f4b9e31156de690d765af245185)
</code></pre>
<p>虽然支持多 active mds并行运行，但官方文档建议保持一个active mds，其他mds作为standby(见下面ceph集群信息中的fsmap部分)：</p>
<pre><code># ceph -s
    cluster ffac3489-d678-4caf-ada2-3dd0743158b6
    ... ...
      fsmap e6: 1/1/1 up {0=yypdnode=up:active}, 1 up:standby
     osdmap e19: 2 osds: 2 up, 2 in
            flags sortbitwise,require_jewel_osds
      pgmap v192498: 576 pgs, 5 pools, 126 MB data, 238 objects
            44365 MB used, 31881 MB / 80374 MB avail
                 576 active+clean
</code></pre>
<h2>三、创建fs并测试挂载</h2>
<p>我们在ceph上创建一个fs：</p>
<pre><code># ceph osd pool create cephfs_data 128
pool 'cephfs_data' created

# ceph osd pool create cephfs_metadata 128
pool 'cephfs_metadata' created

# ceph fs new test_fs cephfs_metadata cephfs_data
new fs with metadata pool 2 and data pool 1

# ceph fs ls
name: test_fs, metadata pool: cephfs_metadata, data pools: [cephfs_data ]
</code></pre>
<p>不过，ceph当前正式版功能中仅支持一个fs，对多个fs的支持仅存在于实验feature中：</p>
<pre><code># ceph osd pool create cephfs1_data 128
# ceph osd pool create cephfs1_metadata 128
# ceph fs new test_fs1 cephfs1_metadata cephfs1_data
Error EINVAL: Creation of multiple filesystems is disabled.  To enable this experimental feature, use 'ceph fs flag set enable_multiple true'
</code></pre>
<p>在物理机上挂载cephfs可以使用mount命令、mount.ceph(apt-get install ceph-fs-common)或ceph-fuse(apt-get install ceph-fuse)，我们先用mount命令挂载：</p>
<pre><code>我们将上面创建的cephfs挂载到主机的/mnt下：

#mount -t ceph ceph_mon_host:6789:/ /mnt -o name=admin,secretfile=admin.secret

# cat admin.secret //ceph.client.admin.keyring中的key
AQDITghZD+c/DhAArOiWWQqyMAkMJbWmHaxjgQ==
</code></pre>
<p>查看cephfs信息：</p>
<pre><code># df -h
ceph_mon_host:6789:/   79G   45G   35G  57% /mnt
</code></pre>
<p>可以看出：cephfs将两个物理节点上的磁盘全部空间作为了自己的空间。</p>
<p>通过ceph-fuse挂载，还可以限制对挂载路径的访问权限，我们来创建用户foo，让其仅仅拥有对/ceph-volume1-test路径具有只读访问权限：</p>
<pre><code># ceph auth get-or-create client.foo mon 'allow *' mds 'allow r path=/ceph-volume1-test' osd 'allow *'
# ceph-fuse -n client.foo -m 10.47.217.91:6789 /mnt -r /ceph-volume1-test
ceph-fuse[10565]: starting ceph client2017-05-03 16:07:25.958903 7f1a14fbff00 -1 init, newargv = 0x557e350defc0 newargc=11
ceph-fuse[10565]: starting fuse
</code></pre>
<p>查看挂载路径，并尝试创建文件：</p>
<pre><code># cd /mnt
root@yypdnode:/mnt# ls
1.txt
root@yypdnode:/mnt# touch 2.txt
touch: cannot touch '2.txt': Permission denied
</code></pre>
<p>由于foo用户只拥有对 /ceph-volume1-test的只读权限，因此创建文件失败了！</p>
<h2>四、Kubernetes跨节点挂载CephFS</h2>
<p>在K8s中，至少可以通过两种方式挂载CephFS，一种是通过Pod直接挂载；另外一种则是通过pv和pvc挂载。我们分别来看。</p>
<h3>1、Pod直接挂载CephFS</h3>
<pre><code>//ceph-pod2-with-secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: ceph-pod2-with-secret
spec:
  containers:
  - name: ceph-ubuntu2
    image: ubuntu:14.04
    command: ["tail", "-f", "/var/log/bootstrap.log"]
    volumeMounts:
    - name: ceph-vol2
      mountPath: /mnt/cephfs/data
      readOnly: false
  volumes:
  - name: ceph-vol2
    cephfs:
      monitors:
      - ceph_mon_host:6789
      user: admin
      secretFile: "/etc/ceph/admin.secret"
      readOnly: false
</code></pre>
<p>注意：保证每个节点上都存在/etc/ceph/admin.secret文件。</p>
<p>查看Pod挂载的内容：</p>
<pre><code># docker ps|grep pod
bc96431408c7        ubuntu:14.04                                                  "tail -f /var/log/boo"   About a minute ago   Up About a minute                                                                        k8s_ceph-ubuntu2.66c44128_ceph-pod2-with-secret_default_3d8a05f8-33c3-11e7-bcd9-6640d35a0e90_fc483b8a
bcc65ab82069        gcr.io/google_containers/pause-amd64:3.0                      "/pause"                 About a minute ago   Up About a minute                                                                        k8s_POD.d8dbe16c_ceph-pod2-with-secret_default_3d8a05f8-33c3-11e7-bcd9-6640d35a0e90_02381204

root@yypdnode:~# docker exec bc96431408c7 ls /mnt/cephfs/data
1.txt
apps
ceph-volume1-test
test1.txt

</code></pre>
<p>我们再在另外一个node上启动挂载同一个cephfs的Pod，看是否可以跨节点挂载：</p>
<pre><code># kubectl get pods

NAMESPACE                    NAME                                    READY     STATUS    RESTARTS   AGE       IP             NODE
default                      ceph-pod2-with-secret                   1/1       Running   0          3m        172.30.192.2   iz2ze39jeyizepdxhwqci6z
default                      ceph-pod2-with-secret-on-master         1/1       Running   0          3s        172.30.0.51    iz25beglnhtz
... ...

# kubectl exec ceph-pod2-with-secret-on-master ls /mnt/cephfs/data
1.txt
apps
ceph-volume1-test
test1.txt

</code></pre>
<p>可以看到不同节点可以挂载同一CephFS。我们在一个pod中操作一下挂载的cephfs：</p>
<pre><code># kubectl exec ceph-pod2-with-secret-on-master -- bash -c "for i in {1..10}; do sleep 1; echo 'pod2-with-secret-on-master: Hello, World'&gt;&gt; /mnt/cephfs/data/foo.txt ; done "
root@yypdmaster:~/k8stest/cephfstest/footest# kubectl exec ceph-pod2-with-secret-on-master cat /mnt/cephfs/data/foo.txt
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
</code></pre>
<h3>2、通过PV和PVC挂载CephFS</h3>
<p>挂载cephfs的pv和pvc在写法方面与上面挂载rbd的类似：</p>
<pre><code>//ceph-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: foo-pv
spec:
  capacity:
    storage: 512Mi
  accessModes:
    - ReadWriteMany
  cephfs:
    monitors:
      - ceph_mon_host:6789
    path: /
    user: admin
    secretRef:
      name: ceph-secret
    readOnly: false
  persistentVolumeReclaimPolicy: Recycle

//ceph-pvc.yaml

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: foo-claim
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 512Mi
</code></pre>
<p>使用pvc的pod:</p>
<pre><code>//ceph-pod2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: ceph-pod2
spec:
  containers:
  - name: ceph-ubuntu2
    image: ubuntu:14.04
    command: ["tail", "-f", "/var/log/bootstrap.log"]
    volumeMounts:
    - name: ceph-vol2
      mountPath: /mnt/cephfs/data
      readOnly: false
  volumes:
  - name: ceph-vol2
    persistentVolumeClaim:
      claimName: foo-claim
</code></pre>
<p>创建pv、pvc：</p>
<pre><code># kubectl create -f ceph-pv.yaml
persistentvolume "foo-pv" created
# kubectl create -f ceph-pvc.yaml
persistentvolumeclaim "foo-claim" created

# kubectl get pvc
NAME        STATUS    VOLUME    CAPACITY   ACCESSMODES   AGE
foo-claim   Bound     foo-pv    512Mi      RWX           4s
# kubectl get pv
NAME      CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM               REASON    AGE
foo-pv    512Mi      RWX           Recycle         Bound     default/foo-claim             24s
</code></pre>
<p>启动pod，通过exec命令查看挂载情况：</p>
<pre><code># docker ps|grep pod
a6895ec0274f        ubuntu:14.04                                                  "tail -f /var/log/boo"   About a minute ago   Up About a minute                                                                        k8s_ceph-ubuntu2.66c44128_ceph-pod2_default_4e4fc8d4-33c6-11e7-bcd9-6640d35a0e90_1b37ed76
52b6811a6584        gcr.io/google_containers/pause-amd64:3.0                      "/pause"                 About a minute ago   Up About a minute                                                                        k8s_POD.d8dbe16c_ceph-pod2_default_4e4fc8d4-33c6-11e7-bcd9-6640d35a0e90_27e5f988
55b96edbf4bf        ubuntu:14.04                                                  "tail -f /var/log/boo"   14 minutes ago       Up 14 minutes                                                                            k8s_ceph-ubuntu2.66c44128_ceph-pod2-with-secret_default_9d383b0c-33c4-11e7-bcd9-6640d35a0e90_1656e5e0
f8b699bc0459        gcr.io/google_containers/pause-amd64:3.0                      "/pause"                 14 minutes ago       Up 14 minutes                                                                            k8s_POD.d8dbe16c_ceph-pod2-with-secret_default_9d383b0c-33c4-11e7-bcd9-6640d35a0e90_effdfae7
root@yypdnode:~# docker exec a6895ec0274f ls /mnt/cephfs/data
1.txt
apps
ceph-volume1-test
foo.txt
test1.txt

# docker exec a6895ec0274f cat /mnt/cephfs/data/foo.txt
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World
pod2-with-secret-on-master: Hello, World

</code></pre>
<h2>五、pv的状态</h2>
<p>如果你不删除pvc，一切都安然无事：</p>
<pre><code># kubectl get pv
NAME                CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM                        REASON    AGE
foo-pv              512Mi      RWX           Recycle         Bound     default/foo-claim                      1h

# kubectl get pvc
NAME                 STATUS    VOLUME              CAPACITY   ACCESSMODES   AGE
foo-claim            Bound     foo-pv              512Mi      RWX           1h
</code></pre>
<p>但是如果删除pvc，pv的状态将变成failed：</p>
<pre><code>删除pvc：

# kubectl get pv
NAME                CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM                        REASON    AGE
foo-pv              512Mi      RWX           Recycle         Failed    default/foo-claim                      2h

# kubectl describe pv/foo-pv
Name:        foo-pv
Labels:        &lt;none&gt;
Status:        Failed
Claim:        default/foo-claim
Reclaim Policy:    Recycle
Access Modes:    RWX
Capacity:    512Mi
Message:    No recycler plugin found for the volume!
Source:
    Type:        RBD (a Rados Block Device mount on the host that shares a pod's lifetime)
    CephMonitors:    [xx.xx.xx.xx:6789]
    RBDImage:        foo1
    FSType:        ext4
    RBDPool:        rbd
    RadosUser:        admin
    Keyring:        /etc/ceph/keyring
    SecretRef:        &amp;{ceph-secret}
    ReadOnly:        false
Events:
  FirstSeen    LastSeen    Count    From                SubobjectPath    Type        Reason            Message
  ---------    --------    -----    ----                -------------    --------    ------            -------
  29s        29s        1    {persistentvolume-controller }            Warning        VolumeFailedRecycle    No recycler plugin found for the volume!

</code></pre>
<p>我们在pv中指定的persistentVolumeReclaimPolicy是Recycle，但无论是cephrbd还是cephfs都不没有对应的recycler plugin，导致pv的status变成了failed，只能手工删除重建。</p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/05/08/mount-cephfs-acrossing-nodes-in-kubernetes-cluster/feed/</wfw:commentRss>
		<slash:comments>25</slash:comments>
		</item>
	</channel>
</rss>
