MooseFS部署和基本操作

环境: CentOS 7 x86_64

配置安装源

curl "http://ppa.moosefs.com/RPM-GPG-KEY-MooseFS" > /etc/pki/rpm-gpg/RPM-GPG-KEY-MooseFS
curl "http://ppa.moosefs.com/MooseFS-stable-el7.repo" > /etc/yum.repos.d/MooseFS.repo

主要有4个角色

  • Master是客户端挂载的入口,负责调度。
  • Chunk是实际存储数据的角色。
  • Client是客户端挂载点
  • Metalogger是可选的,但是为了安全一般都要。用于定期备份MooseFS的Metadata和Log。

Master

yum install -y moosefs-master moosefs-cgiserv

/etc/mfs/mfsexports.cfg 里配置下访问权限。

# grep -v "^#" /etc/mfs/mfsexports.cfg | grep -v "^$"
*           /   rw,alldirs,maproot=0:0,md5pass=6df4d50a41a5d20bc4faad8a6f09aa8f

启动服务

systemctl enable moosefs-master moosefs-cgiserv
systemctl start moosefs-master moosefs-cgiserv

ChunkServer

yum install -y moosefs-chunkserver

/etc/mfs/mfschunkserver.cfg 里指定 master 节点。

# grep -v '^#' /etc/mfs/mfschunkserver.cfg | grep -v '^$'
MASTER_HOST = mfsmaster

/etc/mfs/mfshdd.cfg 里指定存储路径以及空间配额

# grep -v '^#' /etc/mfs/mfshdd.cfg | grep -v '^$'
/mfs/chunk -10GiB

请创建该存储目录,并注意权限改成和配置一致,默认是 mfs 用户。

启动服务

systemctl enable moosefs-chunkserver
systemctl start moosefs-chunkserver

Metalogger

yum install -y moosefs-metalogger

/etc/mfs/mfsmetalogger.cfg 里指定 master 以及备份频率是 1 小时

# grep -v '^#' /etc/mfs/mfsmetalogger.cfg | grep -v '^$'
META_DOWNLOAD_FREQ = 1
MASTER_HOST = mfsmaster.wae.haplat.net

Client

yum install -y moosefs-client fuse

实时挂载

mfsmount -H mfsmaster -o mfsmd5pass=6df4d50a41a5d20bc4faad8a6f09aa8f /mnt/mfs

开机自动挂载,在/etc/fstab里写入

mfsmount                                    /mnt/mfs    fuse    mfsmaster=mfsmaster,mfsmd5pass=6df4d50a41a5d20bc4faad8a6f09aa8f,_netdev 0 0

副本数

MooseFS 默认对文件只存储一个副本,建议设置多一点,但是不能超过chunk数量。

mfssetgoal -r 2 /mnt/mfs

Master恢复

如果Master故障,可以从Metalogger的备份数据,把Master恢复到任意一台机器。所以Metalogger最好不要和Master在同一台机器上。

从Metalogger的/var/lib/mfs拷贝

metadata_ml.mfs.back changelog_ml.0.mfs

这两个文件到要恢复的Master上的 /var/lib/mfs 目录下,执行命令

mfsmaster -a start

即可。

MooseFS 1.7 以及以上版本不支持 mfsmetarestore 命令,请用 mfsmaster -a 替代。

如果不想用命令启动mfsmaster,那么就再执行

mfsmaster stop
systemctl start moosefs-master

cgroup学习笔记

安装

CentOS 6

yum install libcgroup

CentOS 7

yum install libcgroup-tools

使用

默认情况下有几个控制器可以进行限制,分别是

  • cpuset
  • cpu,cpuacct
  • memory
  • devices
  • freezer
  • net_cls
  • blkio
  • perf_event
  • hugetlb

限制CPU负载

我写了一个脚本来消耗CPU

count.sh

#!/bin/bash
N=0
while true; do
    N=$((N+1))
    echo $N
done

执行命令

CMD=/root/count.sh
cgcreate -g cpu:/cpu50
cgset -r cpu.cfs_quota_us=50000 cpu50
cgexec -g cpu:cpu50 $CMD

可见 count.sh 的CPU负载一直维持在50%以内

[root@app-client ~]# ps aux | grep count.sh
root     18594 48.2  0.0 106152  1032 pts/0    R+   09:53   0:11 /bin/bash /root/count.sh
root     18666  0.0  0.0 103304   808 pts/1    S+   09:54   0:00 grep count.sh

此外也可以把已经在运行的进程加入cgroup限制,不需要重启程序。

先执行

/root/count.sh

获取进程号

[root@app-client ~]# ps aux | grep count.sh
root     18695 62.1  0.0 106152  1036 pts/0    R+   09:57   0:04 /bin/bash ./count.sh
root     18697  0.0  0.0 103304   808 pts/1    S+   09:57   0:00 grep count.sh

把进程加入控制器

cgclassify -g cpu:cpu50 18695

Tips:

一个资源组可以加多个进程,资源组限制的是加入该组的进程资源总和。比如上面例子,限制50%的负载,是指所有该组的进程总共消耗50%的负载,而不是每个资源能消耗50%的负载。

可以用 lscgroup 查看配置的控制器

[root@app-client ~]# lscgroup 
cpuset:/
cpu:/
cpu:/cpu50
cpuacct:/
memory:/
devices:/
freezer:/
net_cls:/
blkio:/

可以用命令 cgdelete 进行控制器的删除。

所有控制器格式

请参考 https://www.kernel.org/doc/Documentation/cgroups/

比较常用的有

  • blkio.throttle.read_bps_device – 磁盘读取限速
  • blkio.throttle.write_bps_device – 磁盘写入限速
  • cpu.cfs_quota_us – CPU load限制
  • memory.limit_in_bytes – 最大内存限制
  • cpuset.cpus – 绑定CPU的core,必须和 cpuset.mems 一起使用。

其他

各种控制器,除了使用 cg 系列命令可以修改,也可以直接修改映射到文件系统的目录。默认情况下,CentOS 6 的路径是 /cgroup ,CentOS 7 的路径是 /sys/fs/cgroup

配置文件 /etc/cgconfig.conf 或者目录 /etc/cgconfig.d/ 下可对映射的路径进行配置,有兴趣请自行打开看看。

比如 CentOS 6 下的默认配置是

mount {
    cpuset  = /cgroup/cpuset;
    cpu = /cgroup/cpu;
    cpuacct = /cgroup/cpuacct;
    memory  = /cgroup/memory;
    devices = /cgroup/devices;
    freezer = /cgroup/freezer;
    net_cls = /cgroup/net_cls;
    blkio   = /cgroup/blkio;
}

CentOS 7 部署 Kubernetes

请各流量站点别抄我笔记

截止至2015年9月1日,CentOS 已经把 kubernetes 加入官方源。

目前各相关组件版本如下

  • kubernetes-1.0.0
  • docker-1.7.1
  • flannel-0.2.0
  • etcd-2.0.11

kubernetes环境角色如下

  • 192.168.1.248 etcd server
  • 192.168.1.247 kubernetes master
  • 192.168.1.246 kubernetes node
  • 192.168.1.245 kubernetes node

Tips:

  1. 虽然我下面均用IP进行操作,但是建议部署的时候,建议用hosts或者DNS取代IP。
  2. 建议做部署前,先更新系统到最新版本,免得一些(我遇到过)的安装问题
    yum update -y
    

各组件用途

  • kube master
    • kube-apiserver

      k8s的管理接口

    • kube-scheduer

      k8s调度器,容器的启动、迁移、扩容缩减时候,选择哪个node,就看它了。

    • kube-controller-manager

      k8s对node的控制行为,比如怎么去调用node启动一个容器。

  • kube node
    • kubelet

      负责node的管理,基本所有操作都靠它。

    • kube-proxy

      每个node里的container都在一个私有网络中,kube-proxy的作用就是做一个反向代理,让访问者访问这个node的时候,可以转发到内部对应的container。

Tips: 当然,这是我狭隘的理解,仅供参考。

etcd

  1. 作为kubernetes的数据库,存储了k8s自身的信息、以及各种业务容器信息等。
  2. 存储flannel网络配置信息,供各节点协调。

安装

安装 etcd 节点

yum install etcd -y

安装 k8s master 节点

yum install kubernetes-master -y

安装 k8s node 节点

yum install kubernetes-node flannel docker -y

配置

etcd 节点配置

[root@localhost ~]# egrep -v “^#” /etc/etcd/etcd.conf

ETCD_NAME=default
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.1.248:2379"

启动服务

systemctl enable etcd
systemctl start etcd

k8s master 节点配置

[root@localhost ~]# egrep -v ‘^#’ /etc/kubernetes/apiserver | grep -v ‘^$’

KUBE_API_ADDRESS="--address=0.0.0.0"
KUBE_ETCD_SERVERS="--etcd_servers=http://192.168.1.248:2379"
KUBE_SERVICE_ADDRESSES="--service-cluster-ip-range=10.254.0.0/16"
KUBE_ADMISSION_CONTROL="--admission_control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ResourceQuota"
KUBE_API_ARGS=""

[root@localhost kubernetes]# egrep -v ‘^#’ /etc/kubernetes/controller-manager |grep -v ‘^$’

KUBE_CONTROLLER_MANAGER_ARGS="--node-monitor-grace-period=10s --pod-eviction-timeout=10s"

[root@localhost ~]# egrep -v ‘^#’ /etc/kubernetes/config | egrep -v ‘^$’

KUBE_LOGTOSTDERR="--logtostderr=true"
KUBE_LOG_LEVEL="--v=0"
KUBE_ALLOW_PRIV="--allow_privileged=false"
KUBE_MASTER="--master=http://192.168.1.247:8080"

启动服务

systemctl enable kube-apiserver kube-scheduler kube-controller-manager
systemctl start kube-apiserver kube-scheduler kube-controller-manager

k8s node 节点配置

[root@localhost ~]# egrep -v ‘^#’ /etc/kubernetes/config | grep -v ‘^$’

KUBE_LOGTOSTDERR="--logtostderr=true"
KUBE_LOG_LEVEL="--v=0"
KUBE_ALLOW_PRIV="--allow_privileged=false"
KUBE_MASTER="--master=http://192.168.1.247:8080"

[root@localhost ~]# egrep -v ‘^#’ /etc/kubernetes/kubelet | grep -v ‘^$’

KUBELET_ADDRESS="--address=127.0.0.1"
KUBELET_HOSTNAME="--hostname_override=192.168.1.246"
KUBELET_API_SERVER="--api_servers=http://192.168.1.247:8080"
KUBELET_ARGS="--pod-infra-container-image=kubernetes/pause"

启动服务

systemctl enable kubelet kube-proxy
systemctl start kubelet kube-proxy

k8s node 节点配置 flannel

初始化flannel的etcd配置

etcdctl -C 192.168.1.248:2379 set /coreos.com/network/config '{ "Network": "10.1.0.0/16" }'

[root@bogon ~]# egrep -v ‘^#’ /etc/sysconfig/flanneld | grep -v ‘^$’

FLANNEL_ETCD="http://192.168.1.248:2379"
FLANNEL_ETCD_KEY="/coreos.com/network"

启动服务

systemctl enable flanenld
systemctl restart flanneld, docker

若网络还不通,就重启下node吧。

部署Kubernetes(通用版)

部署Kubernetes(通用版)

本文主旨是脱离各Linux发行版本的特性,描述Kubernetes的部署。

预期效果:

  • 服务器10秒故障容灾
  • 业务副本10秒故障容灾
  • 前段负载均衡

Author: 方云麟 cst05001
Site: http://www.fangyunlin.com/

欢迎引用链接,拒绝转载。

安装

环境

CentOS 7 x76_64

docker

下载最新二进制版本:https://docs.docker.com/installation/binaries/,然后把 docker-latest 复制并重命名为 /usr/local/bin/docker

chown 755 /usr/local/bin/docker

kubernetes

下载最新二进制版本:https://github.com/GoogleCloudPlatform/kubernetes/releases

解压后进到 kubernetes/server 目录,里面有一个名为 kubernetes-server-linux-amd64.tar.gz 的压缩包。再次解压。

解压后把 kubernetes/server/bin/ 目录如下文件拷贝到 /usr/local/bin/ 目录下

hyperkube
kube-apiserver
kube-controller-manager
kubectl
kubelet
kube-proxy
kubernetes
kube-scheduler

etcd

下载最新二进制版本:https://github.com/coreos/etcd/releases

解压后把如下文件拷贝到 /usr/local/bin/ 目录下

etcd
etcdctl

启动脚本

kubernetes是一个C/S结构的服务。
master上的启动脚本

#!/bin/bash

etcd --listen-peer-urls http://0.0.0.0:2380 --data-dir=/var/lib/etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://master:2379 >> /var/log/etcd 2>&1 &
sleep 2
etcdctl set /coreos.com/network/config '{ "Network": "10.1.0.0/16" }'
flanneld --listen=0.0.0.0:8888 >> /var/log/flanneld 2>&1 &
docker -d -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375 >> /var/log/dockerd 2>&1 &
kube-apiserver --logtostderr=true --v=0 --etcd_servers=http://master:2379 --address=0.0.0.0 --allow_privileged=false --service-cluster-ip-range=10.254.0.0/16 --v=0 >> /var/log/kubernetes/kube-apiserver 2>&1 &
kube-controller-manager --logtostderr=true --v=0 --master=http://master:8080 --v=0 --node-monitor-grace-period=10s --pod-eviction-timeout=10s >> /var/log/kubernetes/kube-controller-manager 2>&1 &
kube-scheduler --logtostderr=true --v=0 --master=http://master:8080 >> /var/log/kubernetes/kube-scheduler 2>&1 &

注意,这里所有脚本仅供展现最基本意图之用,创建目录、启动错误等判断,请自行添加。这点问题都搞不定应该不用继续看下去了。

node上的启动脚本

#!/bin/bash

flanneld -etcd-endpoints=http://master:2379 -remote=master:8888 >> /var/log/flannel 2>&1 &
sleep 2
source /run/flannel/subnet.env
docker -d -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375 >> /var/log/dockerd --bip=${FLANNEL_SUBNET} --mtu=${FLANNEL_MTU} 2>&1 &
kubelet --logtostderr=true --v=0 --api_servers=http://master:8080 --address=0.0.0.0 --allow_privileged=false > /var/log/kubernetes/kubelet 2>&1 &
kube-proxy --logtostderr=true --v=0 --master=http://master:8080 > /var/log/kubernetes/kube-proxy 2>&1 &

填坑

kubernetes运行Pods需要伴随运行一个叫 pause的镜像。但是这个镜像被墙了。

请在境外docker服务器执行 docker pull 命令下载镜像

gcr.io/google_containers/pause:latest
gcr.io/google_containers/pause:1.0
gcr.io/google_containers/pause:0.8.0

再用导出镜像

docker save -o pause.tar gcr.io/google_containers/pause
gzip pause.tar

最后把这个包放到 kubernetes 环境所有的 docker 服务器上

 docker load -i pause.tar.gz

测试

创建复制实例配置 phpinfo-rc.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: phpinfo-controller
spec:
  replicas: 2
  # selector identifies the set of Pods that this
  # replication controller is responsible for managing
  selector:
    app: phpinfo
  # podTemplate defines the cookie cutter used for creating
  # new pods when necessary
  template:
    metadata:
      labels:
        # Important: these labels need to match the selector above
        # The api server enforces this constraint.
        app: phpinfo
    spec:
      containers:
      - name: phpinfo
        image: cst05001/phpinfo
        ports:
        - containerPort: 80

执行kubectl命令导入

[root@master rc]# kubectl create -f phpinfo-rc.yaml 
replicationcontrollers/phpinfo-controller
[root@master rc]# kubectl get rc
CONTROLLER           CONTAINER(S)   IMAGE(S)           SELECTOR      REPLICAS
phpinfo-controller   phpinfo        cst05001/phpinfo   app=phpinfo   2
[root@master rc]# kubectl get pods
NAME                       READY     STATUS    RESTARTS   AGE
phpinfo-controller-e6ej4   1/1       Running   0          14s
phpinfo-controller-ttrw2   1/1       Running   0          14s
[root@master rc]# 

创建前段负载分发配置 phpinfo-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: phpinfo-service
spec:
  ports:
  - port: 8000 # the port that this service should serve on
    # the container on each pod to connect to, can be a name
    # (e.g. www) or a number (e.g. 80)
    targetPort: 80
    protocol: TCP
  # just like the selector in the replication controller,
  # but this time it identifies the set of pods to load balance
  # traffic to.
  selector:
    app: phpinfo

执行kubectl命令导入

[root@master service]# kubectl create -f phpinfo-service.yaml 
services/phpinfo-service
[root@master service]# kubectl get services
NAME              LABELS                                    SELECTOR      IP(S)           PORT(S)
kubernetes        component=apiserver,provider=kubernetes   <none>        10.254.0.1      443/TCP
phpinfo-service   <none>                                    app=phpinfo   10.254.160.53   8000/TCP
[root@master service]# 

在有启动 kube-proxy 服务的机器上可通过 http://10.254.160.53:8000 访问到实例,并可通过 页面 System 里的主机名确认负载均衡实例的效果。

可持续集成工具Jenkins用于代码发布+docker打包镜像的应用

官方网站:https://jenkins-ci.org/

安装jenkins

以CentOS 7为例,通过 yum 安装

wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
yum install jenkins

或者直接 rpm 安装,到 http://pkg.jenkins-ci.org/redhat/ 下载最新版本 rpm 。其他系统的安装包自己到官网找。

修改 /etc/sysconfig/jenkins

JENKINS_USER="jenkins"

改成

JENKINS_USER="root"

jenkins默认使用8080端口,如果冲突,把

JENKINS_PORT="8080"

改成别的端口。

启动jenkins服务

systemctl restart jenkins
  1. 用浏览器登陆Jenkins界面。新建一个项目,比如叫做test。
  2. 勾选“自由风格”项目。
  3. 源码管理选择“SVN”,补充需要的信息。
    1. Repository URL是svn checkout的路径。
    2. Local module directory可不填,填了就是代码checkout下来的相对目录。我这里填写“test”
  4. 构建里选择“Execute shell”,内容如下
    export BUILDROOT=/docker/build/test/
    /usr/bin/rsync -vzrtopg --delete test $BUILDROOT/files/test
    cd $BUILDROOT
    /usr/bin/docker build -t registry.ws.com/test .
    /usr/bin/docker push registry.ws.com/test
    

保存配置后,点“构建”就可以实现拉去代码、打包成镜像、推送到镜像服务器这几个步骤了。

docker1.6.2导致的Cannot chdir: No such file or directory /usr/bin/tar: Error is not recoverable: exiting now 的bug

今天安装dockerui ,遇到一个报错:docker run异常退出。下面是默认用 docker run -d 的报错,以及为了诊断,去掉 -d 的详细信息

[root@docker147 ~]# docker run -d -p 9000:9000 --privileged -v /var/run/docker.sock:/var/run/docker.sock dockerui/dockerui
919bf6a1f912dd0f823e6b20a805b70658b22f86353f93bb6d0790440ad11bca
FATA[0000] Error response from daemon: : exit status 2  
[root@docker147 ~]# docker run -p 9000:9000 --privileged -v /var/run/docker.sock:/var/run/docker.sock dockerui/dockerui
Timestamp: 2015-06-17 10:36:43.285451213 +0800 CST
Code: System error

Message: [/usr/bin/tar -cf /var/lib/docker/tmp/bc2a56ed9154afccb801b1552a3576b6592a40a764afa4f402b806d1efd57e02908036333/_tmp.tar -C /var/lib/docker/devicemapper/mnt/bc2a56ed9154afccb801b1552a3576b6592a40a764afa4f402b806d1efd57e02/rootfs/tmp .] failed: /usr/bin/tar: /var/lib/docker/devicemapper/mnt/bc2a56ed9154afccb801b1552a3576b6592a40a764afa4f402b806d1efd57e02/rootfs/tmp: Cannot chdir: No such file or directory
/usr/bin/tar: Error is not recoverable: exiting now
: exit status 2

Frames:
---
0: setupRootfs
Package: github.com/docker/libcontainer
File: rootfs_linux.go@30
---
1: Init
Package: github.com/docker/libcontainer.(*linuxStandardInit)
File: standard_init_linux.go@52
---
2: StartInitialization
Package: github.com/docker/libcontainer.(*LinuxFactory)
File: factory_linux.go@223
---
3: initializer
Package: github.com/docker/docker/daemon/execdriver/native
File: init.go@35
---
4: Init
Package: github.com/docker/docker/pkg/reexec
File: reexec.go@26
---
5: main
Package: main
File: docker.go@29
---
6: main
Package: runtime
File: proc.go@63
---
7: goexit
Package: runtime
File: asm_amd64.s@2232
FATA[0000] Error response from daemon: : exit status 2 

关键信息是

Message: [/usr/bin/tar -cf /var/lib/docker/tmp/bc2a56ed9154afccb801b1552a3576b6592a40a764afa4f402b806d1efd57e02908036333/_tmp.tar -C /var/lib/docker/devicemapper/mnt/bc2a56ed9154afccb801b1552a3576b6592a40a764afa4f402b806d1efd57e02/rootfs/tmp .] failed: /usr/bin/tar: /var/lib/docker/devicemapper/mnt/bc2a56ed9154afccb801b1552a3576b6592a40a764afa4f402b806d1efd57e02/rootfs/tmp: Cannot chdir: No such file or directory
/usr/bin/tar: Error is not recoverable: exiting now

搜索步骤如下图

搜索步骤

所以判断是和 docker 版本相关的 known issue

本机docker版本是

[root@docker147 ~]# docker version
Client version: 1.6.0
Client API version: 1.18
Go version (client): go1.4.2
Git commit (client): 8aae715/1.6.0
OS/Arch (client): linux/amd64
Server version: 1.6.0
Server API version: 1.18
Go version (server): go1.4.2
Git commit (server): 8aae715/1.6.0
OS/Arch (server): linux/amd64

升级到官方最新版本

[root@docker147 ~]# docker-1.6.2 version
Client version: 1.6.2
Client API version: 1.18
Go version (client): go1.4.2
Git commit (client): 7c8fca2
OS/Arch (client): linux/amd64
Server version: 1.6.0
Server API version: 1.18
Go version (server): go1.4.2
Git commit (server): 8aae715/1.6.0
OS/Arch (server): linux/amd64

问题解决。

附上升级docker办法:https://docs.docker.com/installation/binaries/

用pipework给container设置独立IP

官方网站:https://github.com/jpetazzo/pipework
以CentOS 7为例:

给宿主机创建网桥

[root@docker1 ~]# cat /etc/sysconfig/network-scripts/ifcfg-br0 
TYPE="Bridge"
BOOTPROTO="static"
IPADDR="192.168.150.128"
PREFIX="24"
NAME="br0"
DEVICE="br0"
ONBOOT="yes"

设置物理网卡桥接到网桥

[root@docker1 ~]# cat /etc/sysconfig/network-scripts/ifcfg-eno16777736 
TYPE="Ethernet"
BOOTPROTO="none"
DEFROUTE="yes"
PEERDNS="yes"
PEERROUTES="yes"
IPV4_FAILURE_FATAL="no"
IPV6INIT="yes"
IPV6_AUTOCONF="yes"
IPV6_DEFROUTE="yes"
IPV6_PEERDNS="yes"
IPV6_PEERROUTES="yes"
IPV6_FAILURE_FATAL="no"
NAME="eno16777736"
UUID="872e13a4-d0a5-4392-a270-a157f70a5bd2"
DEVICE="eno16777736"
ONBOOT="yes"
BRIDGE="br0"

用pipework启动容器

pipework br0 $(docker run -d -it -p 8080:80  nginx) 192.168.150.200/24@192.168.150.2

pipework会用bridge方式启动容器,run的-p参数失效。启用端口以expose为准。

container 关闭后如果重新start,网络需重新配置。把

ipework br0 $(docker run -d -it -p 8080:80  nginx) 192.168.150.200/24@192.168.150.2

改成

ipework br0 service1 192.168.150.200/24@192.168.150.2

service1是容器的–name

Docker入门笔记(4) – 搭建私有 registry

在实际工作中可能会遇到几个场景

  • 紧急部署业务,需要在几分钟内 pull 并 run 一个 contaienr。并且需要稳定,不能总是出现莫名其妙的 pull 异常。
  • 业务代码和数据私有,为了安全考虑,不便放于公有 registry。

这可以描述在内网或者在私有环境部署自己的 registry 的必要性。

这里有两个链接,推荐大家参考:

部署步骤

部署没有账号验证功能 registry

直接利用 docker 一键部署的特性,安装 registry image(这就是为什么我不推荐 docker 官方部署方法的原因。而且这个 registry image 也是 docker 官方提供的,请放心使用。)

docker run \
        --name registry-server \
        -d \
        -e SETTINGS_FLAVOR=local \
        -e SEARCH_BACKEND=sqlalchemy \
        -e STORAGE_PATH=/docker/registry \
        -v /docker/registry:/docker/registry \
        -p 192.168.0.112:80:5000 \
        registry

这里有两个参数比较重要:

  • -v 参数,把 registry container 里的 image 存储目录映射到主机上。
    • 因为存储是永久性的,如果把 image 存在 container 中,一旦 contaienr 给销毁,那么数据就没了。
    • 而且 image 的文件系统必须有容量定义,一般是8G或者16G,所以一旦 image 多了,会导致主机磁盘还有空间,而 container 却报磁盘空间满,无法正常工作。不要在 container 里存放永久性数据
  • -p 参数,registry 默认监听 5000 端口,而 docker 默认使用 80端口,所以把 container 的 5000 端口映射到主机的 80 端口,免得在 dockre push 或者 pull 的时候加端口麻烦。

问题:为什么我 run 的是 containerops.cn/docker/registry ,而不是 registry ?这个请看我之前笔记。其实都可以,但是因为国内访问 docker.com 的 registry 太慢,所以我直接使用国内的 registry 缓存。

为 registry 添加账号验证

出于安全性考虑,账号是必须的。registry 在前端加一个 web server,用 web auth 模块专门做账号验证。

创建 htpasswd 账号密码数据库(htpasswd工具是apache组件的一部分,以下操作均以Ubuntu 14.04 TLS 为例)

mkdir /etc/nginx
apt-get install apache2-utils
htpasswd -c /etc/nginx/docker-registry.htpasswd USERNAME
vim /etc/nginx/nginx.conf

nginx.conf 内容如下:

worker_processes auto;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen  80 default_server;
        server_name _;
        location / {
            proxy_pass http://192.168.0.112:80;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-Ip $remote_addr;
            proxy_set_header X-Forwarded-For $remote_addr;
            auth_basic              "Restricted";
            auth_basic_user_file     nginx.htpasswd;
        }
        location /_ping {
            auth_basic off;
            proxy_pass http://192.168.0.112:80;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-Ip $remote_addr;
            proxy_set_header X-Forwarded-For $remote_addr;
        }
        location /v1/_ping {
            auth_basic off;
            proxy_pass http://192.168.0.112:80;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-Ip $remote_addr;
            proxy_set_header X-Forwarded-For $remote_addr;
        }
    }
}
  • 上面命令中 USERNAME 代表你指定的用户名,命令会交互式要求你设置密码
  • 如果需要创建多个账号,可以追加命令 htpasswd 不带 -c (create),否则会清空之前数据库。
    htpasswd /etc/nginx/docker-registry.htpasswd USERNAME_2
    
  • nginx.conf 中,默认路径 / 就是要验证的地方。而用于 registry 可用性监测的 /_ping 和 /v1/_ping 两个路径切勿加验证。否则会导致 registry 无法正常工作。

最后启动 nginx,就可以使用带验证功能的 registry了。

docker run \
    --name registry-nginx \
    -d \
    -p 117.***.148.***:80 \
    -v /etc/nginx/nginx.conf:/etc/nginx/nginx.conf:ro \
    -v /etc/nginx/docker-registry.htpasswd:/etc/nginx/nginx.htpasswd:ro \
    nginx:latest

问:上述架构有什么特点?

答:内网免验证,外网有验证。

问:要做成内外网均要验证,怎么改?是否涉及到端口冲突?要怎么处理?

Docker入门笔记(3) – image和container

Docker入门笔记(3) – image和container

docker最简单的运行关系是这样的

docker生命周期

镜像仓库和镜像/registry and image

registry是docker镜像的仓库。默认仓库是 https://registry.hub.docker.com/
镜像(image)是存放在registry里的。

我们可以直接在这里获取到centos、ubuntu等操作系统的镜像。这种系统镜像一般用来做二次定制,定制私有业务。

此外还有一些具体定制的镜像,比如tomcat、apache、nginx、mysql、mongodb、redis等。这些镜像的操作系统都裁剪的很小,所以一些简单需求直接拿这些镜像来用,会方便一些。centos、ubuntu这些系统基础镜像大小都在200M以上,而像busybox、flannel这些具体定制的镜像都不到50M。

以镜像为基础运行一个容器/run a container from image

我先拉取一个busybox镜像下来。

root@docker:~# docker pull busybox
latest: Pulling from busybox
511136ea3c5a: Pulling fs layer
511136ea3c5a: Download complete
df7546f9f060: Download complete
ea13149945cb: Download complete
4986bf8c1536: Download complete
Status: Downloaded newer image for busybox:latest
root@docker:~# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
busybox             latest              4986bf8c1536        7 weeks ago         2.433 MB
busybox             buildroot-2014.02   4986bf8c1536        7 weeks ago         2.433 MB
root@docker:~#

可以看到busybox这种定制版本的镜像就很小,才3M不到。以这个镜像为基础运行一个contaienr:

root@docker:~# docker run -it busybox
/ # whoami
root
/ # date
Sat Feb 21 06:07:57 UTC 2015
/ # uname -r
3.13.0-32-generic
/ #

我们可以登陆 https://registry.hub.docker.com/ 很直观的查询有哪些可用的镜像以及使用方法。
我比较常用的镜像有

  • centos:centos6
  • tomcat:7
  • mongo:2

container 状态

启动另外一个终端,查看container状态:

root@docker:~# docker ps
CONTAINER ID        IMAGE                       COMMAND             CREATED             STATUS              PORTS               NAMES
861856052a59        busybox:buildroot-2014.02   "/bin/sh"           10 seconds ago      Up 10 seconds                           evil_newton

这时候输入exit或者直接ctrl+D可以退出这个container。这之后container会变成exited状态,并不会停留在后台执行。也就是说,container默认不会自动销毁。

# root@docker:~# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
root@docker:~# docker ps -a
CONTAINER ID        IMAGE                       COMMAND             CREATED             STATUS                     PORTS               NAMES
861856052a59        busybox:buildroot-2014.02   "/bin/sh"           3 minutes ago       Exited (0) 4 seconds ago                       evil_newton
root@docker:~#
  • image只是一个文件,运行起来之后的东西叫做container
  • 一个image可以启动多个contaienr
  • 每个container之间的读写变更不会相互影响
  • container的读写变更不会影响到image
  • 默认请款下,contaienr退出后会保存在磁盘上,不会自动销毁
  • docker ps只能查看up状态的contaienr,要查看所有状态的container需要加 -a 参数

上面docker run命令,加了-i -t 两个参数。-i保证交互式输入。-t表示分配tty终端。这两个都和交互式输入输出有关系,默认加上即可,和本主题无关,我不想深究。大家在后期可以试试不加这两个参数的效果,对比一下。现在对比,除了无法输入输出,其他应该体验不出来。这个话题以后也许会再讨论。

前台和后台

上面通过 -i -t 的方式把 container 挂到终端上运行。这个只是在学习和打包 image 的时候会用到。如果是在线上业务中,把 contaienr 直接作为后台程序运行会更合适点。使用参数 -d 就行了。

root@docker:~# docker run -it -d busybox
0976249c0af6baab618043187ce067faba27832e8cf85a7d434ba6b2c25256aa
root@docker:~# docker ps
CONTAINER ID        IMAGE                       COMMAND             CREATED             STATUS              PORTS               NAMES
0976249c0af6        busybox:buildroot-2014.02   "/bin/sh"           3 seconds ago       Up 2 seconds                            angry_archimedes
root@docker:~# 

-d 后 busybox 不会在前台运行,docker直接给出 container id ,而在 docker ps 命令中可以看到 busybox 是出于运行状态。

网络服务相关的 container

基本所有业务都是需要通过网络对外提供服务的。所以我们可以通过 docker run -p 参数来实现。

docker run -p 80,在主机上找一个随机端口和 container 的 80 端口做映射(个人觉得比较没用)
docker run -P,在主机上找对应的若干个随机端口,和 image 文件指定的 expose 端口做映射(个人觉得比较没用)
docker run -p 10080:80,把主机的 10080 端口和 contaienr的 80 端口做映射。这时候业务要通过主机IP访问 container 里的 80 端口业务,要访问的可是 10080 端口。
docker run -p 192.168.0.10:10080:80,把主机的 192.168.0.10 IP的 10080 端口和 container 里的 80 端口做映射。
docker run -p -p -p,如果有多个端口需要映射,可以写多次 -p 参数。

root@docker:~# docker run -p 80:80 -p 23:23 -d -it busybox
13af89b8ef87c857cd1efb88f8fd5b8ffeca3faea691bcaa73c224fcc126183d
root@docker:~# docker ps
CONTAINER ID        IMAGE                       COMMAND             CREATED             STATUS              PORTS                                    NAMES
13af89b8ef87        busybox:buildroot-2014.02   "/bin/sh"           4 seconds ago       Up 4 seconds        0.0.0.0:23->23/tcp, 0.0.0.0:80->80/tcp   fervent_bohr       
root@docker:~# netstat -ntpl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      916/sshd
tcp6       0      0 :::23                   :::*                    LISTEN      1780/docker-proxy
tcp6       0      0 :::80                   :::*                    LISTEN      1789/docker-proxy
tcp6       0      0 :::22                   :::*                    LISTEN      916/sshd

大家观察下上面输出。

为什么我的 container 总是退出?

docker run 一个 container 的时候,最后都需要加一个 CMD 参数,表示我 container 要启动什么程序。一旦这个程序退出或者挂到后台,就会导致整个 container 退出结束。从经验上看,下面涵盖了大多数情况:

  • 程序无法启动
    • 没有指定正确的程序路径或者程序不存在
    • 启动程序的参数正确,比如没有这个参数
    • 程序配置文件有错误
  • 程序默认挂到后台执行了

比如我们执行 docker run -it -d busybox,其实 busybox 有一个默认的 CMD 值,是 /bin/sh ,也就是等同于 docker run -it -d busybox /bin/sh 。这两句命令是一样的。 /bin/sh 这个程序会一直等待键盘输入,所以不会退出。

定制自己需要的镜像

基于 container 进行 commit

光有基础系统的 image 只能玩一玩,没什么实际意义。我们尝试定制一个有自己功能的 image。

创建一个 container ,并且进入 container 中进行操作:

docker run -it busybox /bin/sh

在 container 中创建文件 /usr/bin/run.sh

root@docker:~# docker run -it busybox /bin/sh
/ # pwd
/
/ # vi /usr/bin/run.sh

内容如下:

#!/bin/sh

COUNT=0
while(true); do
        COUNT=$(($COUNT+1))
        echo $COUNT
        sleep 2
done

授予执行权限:

chmod 755 /usr/bin/run.sh

测试,直接输入命令 run.sh 执行,可见终端每2秒就会输出一个计数,并且永不退出(while(true))。

/ # run.sh
1
2
3
4
5
6
...

+ C退出脚本,再exit或者 + D退出 container 。这时候可以通过 docker ps -a 看到 container 处于 exitted 状态,并且获得 container ID 是 d321110c0473 。执行命令

docker commit d3 cst05001/study

可以把该 container 提交为 docker 默认镜像仓库下,名为 cst05001/study 的镜像。不过这只是一个标记,镜像只存在于本地,并未真正的传到镜像服务器,只能本地使用。

root@docker:~# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
cst05001/study      latest              a836bfbda876        2 minutes ago       2.433 MB
busybox             buildroot-2014.02   4986bf8c1536        7 weeks ago         2.433 MB
busybox             latest              4986bf8c1536        7 weeks ago         2.433 MB

我们尝试运行我们定制的这个镜像:

root@docker:~# docker run -it cst05001/study run.sh
1
2
3
...

成功。

Dockerfile(推荐)

通过 Dockerfile 进行镜像定制是在生产环境更高效更稳定的方法。不过我会放到以后再表。

push image

之前有说,我们定制的镜像并未传送到镜像服务器。如果希望共享出这个镜像给多台服务器使用,则需要把镜像推送到镜像服务器上(registry)。步骤大致如下:

  • 在镜像服务器上注册自己的账号,创建自己的仓库(如果是自己创建的registry可以略过这一步)
  • docker login,输入账号、密码、邮箱三个信息进行登陆验证
  • docker push <镜像名(tag)全程>

比如我们提交刚才的 cst05001/study,则可以这么做

root@docker:~# docker login
Username: cst05001
Password:
Email: 65141838@qq.com
Login Succeeded
root@docker:~# docker push cst05001/study
The push refers to a repository [cst05001/study] (len: 1)
Sending image list
Pushing repository cst05001/study (1 tags)
511136ea3c5a: Image already pushed, skipping
df7546f9f060: Image already pushed, skipping
ea13149945cb: Image already pushed, skipping
4986bf8c1536: Image already pushed, skipping
a836bfbda876: Image successfully pushed
Pushing tag for rev [a836bfbda876] on {https://cdn-registry-1.docker.io/v1/repositories/cst05001/study/tags/latest}

现在我们就可以在全球所有接入互联网的机器上直接通过命令

docker pull cst05001/study

获取这个镜像,或者直接用命令

docker run cst05001/study run.sh

获取并运行这个镜像了。

第三方 registry

由于一些不方便讲的原因,在大陆地区使用官方 registry 经常会有下面几个问题

  • pull速度慢
  • pull失败
  • push速度慢
  • push失败
  • 各种奇怪问题

所以就催生了第三方的 registry。我比较信赖的国内 registry 有 docker.cn,最近刚发现改名成 https://containerops.cn/ 了。这个 registry 曾经缓存了 docker 官方 registry 的所有内容,据说一星期更新一次。用了 又拍云 的加速服务,还可以,实测大概两三百kbytes/s。可是改版后官方镜像不见了。所以如果你懒得搭建自己的私有registry,可以直接用他的。

推送镜像到 containerops.cn

前提是已经在 containerops.cn 注册了账号,并且创建了镜像库。登陆账号(如果你已经登陆了,可以略过这个步骤)。我偷个懒,把刚才的 cst05001/study 镜像做一个别名,也绑定到 docker。是的,image 名称也起到指定 registry地址、仓库的作用。再然后推送镜像就可以了。

root@docker:~# docker login containerops.cn
Username: cst05001
Password:
Email: 65141838@qq.com
Login Succeeded
root@docker:~# docker tag cst05001/study containerops.cn/cst05001/study
root@docker:~# docker push containerops.cn/cst05001/study
The push refers to a repository [containerops.cn/cst05001/study] (len: 1)
Sending image list
Pushing repository containerops.cn/cst05001/study (1 tags)
Image 511136ea3c5a already pushed, skipping
Image df7546f9f060 already pushed, skipping
Image 4986bf8c1536 already pushed, skipping
Image ea13149945cb already pushed, skipping
a836bfbda876: Image successfully pushed
Pushing tag for rev [a836bfbda876] on {https://containerops.cn/v1/repositories/cst05001/study/tags/latest}
root@docker:~#

部署私有 registry

这个是挺重要的一件事情,我会单独开一章来表。

小建议

请执行 docker run –help ,把每一个参数都一个字一个字的读一遍。这样挺好的。

问题

本文在使用第三方 registry 的时候,漏了一个重要的点没有说。如果有亲自测试,一定会遇到,并且不解决不行,但是会有明显的报错以及提示解决方法。请问这是什么?