孙大喜

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

 

K8s

SVN

Subversion 官网

http://subversion.tigris.org/

Openshift

案例新浪云SAE openshift本身是一种套件

技术描述
Kubernutes 管理容器的组件; 集群管理 %80内容
Apache 对外接口服务, 以及账号的管理
Git 代码的管理, svn
Etd 非关系型数据库
docker 容器 hub.docker.com 国内的docker仓库, 时速云 hub.tenxcloud.com 阿里云的docker

默认密码是Asimov

隔离namespace的定义

Namespace系统调用参数隔离内容
UTS CLONE_NEWUTS 主机名与域名
IPC CLONE_NEWIPC 信号量、消息队列和共享内存
PID CLONE_NEWPID 进程编号
Network CLONE_NEWNET 网络设备、网络栈、端口等等
Mount CLONE_NEWNS 挂载点(文件系统)
User CLONE_NEWUSER 用户和用户组

Docker官方提供的公共镜像仓库public registry http://registry.hub.docker.com/ 红帽官方提供的公共镜像仓库 http://registry.access.redhat.com/

Doker

Docker被称为第三代Pass平台 DotCloud, 主要基于Pass平台为开发者.

Docker的核心技术cgroups. 将一组程序定义为一个group, 在这个group中, 有分配好的特定比例的cpu时间, io时间, 可用内存大小等. 最早是由google工程师提出. cgroups的重要概念是"子系统", 也就是资源控制器, 每个子系统就是一个资源的分配器. 比如CPU子系统是控制CPU时间分配的. 首先需要挂载子系统, 然后才有control group. 笔记: http://blog.opskumu.com/docker.html

容器技术

LXC是linux containers的简称, 是一种基于容器的操作系统层级的虚拟化技术. 借助于namespace的隔离机制和cgroup限额功能, LXC提供了一套统一的API和工具来建立和管理容器. Namespace: 命名空间, 相当于平行宇宙, 每个命名空间相互隔离, 互不干扰 LXC: 提供一个共享kernel的OS级别的虚拟化方法, 在执行时不用重复加载kernel, 由于共享kernel, 会导致一些kernel参数没办法在某个特定容器内进行修改. AUFS: docker的文件系统, 是一个能透明覆盖一或多个现有文件系统的层状文件系统. 支持将不同目录挂载到同一个虚拟文件系统下, 可以把不同的目录联合在一起, 组成一个单一的目录. 这是一种虚拟的文件系统, 文件系统不需要格式化, 直接挂载即可. 支持写入复刻(copy on write). 差异存储, 最大化公用底层的文件系统. 容器不建议使用sshd服务. docker exec命令可以进入容器排查问题.

Docker部署

baseurl = https://yum.dockerproject.org/repo/main/centos/7 gpgkey = https://yum.dockerproject.org/gpg

禁用firewalld, 启动iptables 查看docker的基本信息

docker info

查看docker版本

docker version

查看容器的日志

docker logs [containerID]

Docker配置参数

配置文件为/etc/sysconfig/docker OPTIONS用来控制Docker Daemon进程参数 -H 表示Docker Daemon绑定的地址, -H=unix:///var/run/docker.sock -H=tcp://0.0.0.0:2375 --registry-mirror 表示Docker Registry的镜像地址 --registry-mirror=http://4bc5abeb.m.daocloud.io --insecure-registry 表示(本地) 私有Docker Registry的地址. --insecure-registry ${privateRegistryHost}:5000 --selinux-enabled是否开启SELinux,默认开启 --selinux-enabled=true --bip 表示网桥docker0使用指定CIDR网络地址, --bip=172.17.42.1 -b 表示采用已经创建好的网桥, -b=xxx

OPTIONS=-H=unix:///var/run/docker.sock -H=tcp://0.0.0.0:2375 --registrymirror=http://4bc5abeb.m.daocloud.io --selinux-enabled=true

docker的日志默认放到/var/log/messages中 查找docker image

docker search java

docker run. docker run的命令结束了, container也就结束了

docker run [options] IMAGE[:TAG][Command][ARG...]
docker run -it java ps 

-d: 在后台执行 docker exec 可以进入到该容器中. 或者使用attach重新连接容器的会话. 如果是attach在退出的时候可能会将容器关闭 交互就使用 -i -t docker run 时 没有指定--name, namedaemon会自动生成随机字符串UUID docker基础命令, create只是创建但是不会运行

docker create/start/stop/pause/unpause

创建mysql容器

docker create --name mysqlsrv1 -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 mysql
-e: 提供环境变量

然后对虚拟机访问3306端口就可以访问容器的mysql服务

docker exec -it mysqlsrv1 /bin/bash

查看环境变量, docker的配置文件一般使用环境变量来定义

docker exec mysqlsrv1 env

运行停止后的容器也会占用磁盘空间, 在一次性运行的容器后面添加 run -rm, 在执行后删除 将容器变成镜像

docker commit <container> [repo:tag]

docker可以在容器中额外挂载一些目录, 比如可以添加一些ping的工具

docker run --privileged -v /sbin:/mnt/sbin -v /bin:/mnt

Docker命令

coreOS开发, 于redhat合作, google提供Kubernetes管理工具. images相当于一个模板, 实质就是一个归档包tar. 分为readonly只读档和写的层. image生成容器. 一个镜像可以对应多个容器, 如果容器中需要进行写操作, 则需要从images中copy一个.

直接下载一个image

docker search rhel7
docker pull workstation.pod6.example.com:5000/library/rhel7

执行一个容器

docker run --help
docker run -itd 275be1d3d070 "/bin/bash"
-i: interactive
-t: tty
-d: 后台运行

修改docker image标签

docker tag docker.io/ubuntu ubuntu:mini

查看镜像的详细信息

docker inspect [id]

删除镜像

docker rmi [id]
	-f: 强制删除

根据本地镜像制作镜像文件

docker commit -m "Added a new file" -a "Docker Newbee" b1624e625c32 test
	-a: 作者信息
	-m: 提交信息
	-p: 提交时暂停容器运行

模板可以通过openvz的网站进行下载https://download.openvz.org/template/precreated/

cat centos-6-x86_64-minimal.tar.gz |docker import - centos6:latest

保存和导入镜像, 使用save和load

docker save -o ubuntu.tar test:latest 
docker load --input ubuntu.tar
docker load < ubuntu.tar

还可以使用import导入容器

docker import 
cat test_for_run.tar| docker import - test/ubuntu:v1.0

docker既可以使用docker load命令来导入镜像存储文件到本地镜像库, 也可以使用docker import命令来导入一个容器快照到本地镜像仓库. 容器快照文件将丢弃所有的历史记录和元数据信息, 而镜像存储文件将保存完整记录, 体积比较庞大. 新建容器 docker run相当于docker create 然后在docker exec

docker create  
	-t: 提供一个伪终端pseudo-tty
	-i: 让容器保持打开状态
	-d: 守护状态运行Daemonized

查看容器日志

docker log <container id>

查看容器状态

docker ps
	-a: 可以查看离线的容器

连接容器, attach在离开容器的时候会关闭容器

docker attach
docker exec -it
docker exec -it 213c4841716d "bin/bash"

删除容器

docker rm
	-f: 强行终止并删除一个运行中的容器
	-l: 删除容器的连接, 单保留容器
	-v: 删除容器挂载的数据卷

制作容器包

febootstrap -i bash -i wget -i net-tools -i openssh-server -i openssh-client rhel71 rhel71doc http://content.example.com/ose3.0/x86_64/dvd/rhel-7-server-rpms/

import image到docker中

cd rhel71doc/
tar -c .|docker import - rehl71_stu6

删除容器

docker rm

修改image标签

docker tag dl.dockerpool.com:5000/ubuntu:latest ubuntu:latest

查看image详细信息, 返回的是一个json格式的消息, 也可以使用-f参数来指定某一个参数

docker inspect [imageid]

新建容器 docker create, 让docker分配一个伪终端pseudo-tty

docker create -it centos:6 "/bin/bash"

启动容器可以使用docker run 或者docker start

docker run centos /bin/echo "hello world"

终止容器, 使用docker stop. 它首先向容器发送SIGTERM信号, 等待一段时间后(默认为10s), 在发送SIGKILL信号终止容器 也可以是用docker start

docker start [container id]

使用docker attach附着到容器中

docker attach [container id]

查看某个容器的进程pid

docker inspect --format "{{ .State.Pid }}" [container id]

使用nsenter登录到容器中

nsenter --target 6803 --mount --uts --ipc --net --pid

使用registry镜像创建私有仓库

创建本地的镜像仓库


使用Dockerfile制作镜像

创建镜像的方法有三种, 基于已有镜像创建, 基于本地模板导入以及Dockerfile创建

Dockerfile 文件配置

FROM rhel7
MAINTAINER Sun Ying 
EXPOSE 80
RUN yum -y install httpd
RUN echo "Sample welcome page" >/var/www/html/index.html
CMD /usr/sbin/httpd -DFOREGROUND

执行buildfile, 指在当前目录下查找Dockerfile, 并且将image命名为ying/webservice

docker build -t ying/webservice .

因为制作buildfile的时候是在容器中执行的, 我们如果需要添加一些文件到容器中. 则需要使用ADD进行添加 ADD hello.sh /bin/hello.sh

FROM rhel7
MAINTAINER Sun Ying 
EXPOSE 80
ADD hello.sh /bin/hello.sh
ENV WORD hello world
RUN /bin/hello.sh

复杂案例: 制作ubuntu+java+tomcat+ssh server镜像 ENTRYPOINT是告诉镜像需要执行什么指令, 即在镜像被使用的时候执行的指令

FROM ubuntu
MAINTAINER Ying "ying.sun@example.com"
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" >/etc/apt/sources.list
RUN apt-get update
RUN apt-get install -y openssh-server
RUN mkidr -p /var/run/sshd
RUN echo "root:123456"|chpasswd
RUN apt-get install python-software-properties
RUN add-apt-repository ppa:webupd8team/java
RUN apt-get update
RUN apt-get install -y vim wget curl oracle-java7-installer	 tomcat7
# 设置JAVA_HOME环境变量
RUN update-alternatives --display java 
RUN echo "JAVA_HOME=/usr/lib/jvm/java-7-oracle" >> /etc/environment 
RUN echo "JAVA_HOME=/usr/lib/jvm/java-7-oracle" >> /etc/default/tomcat7
# 开启容器的22, 8080端口
EXPOSE 22
EXPOSE 8080
# 设置tomcat7初始化运行
ENTRYPOINT service tomcat7 start && /usr/sbin/sshd -D

Supervisor可以启动多个进程. supervisoer作为守护进程监管多个进程运行. 注意, docker的程序一定要放在前台执行. superviord还会去监管他所启动的进程

[supervisord]
nodaemon=true
[program:sshd]
command=/usr/sbin/sshd -D
[program:apache2]
command=/bin/bash -c "source /etc/apache2/envvars && exec /usr/sbin/apache2 -DFOREGROUND"

一般来说一个容器中应该只执行一个程序, docker只会监测容器中的前台程序是否执行正常. 程序的配置文件如果放在镜像中会对镜像的升级带来运维的成本. 一般来说可以放在环境变量中(ENV). 此外, 日志输出也是docker的一个问题. 一般来说日志, 需要绑定到持久存储. 或者使用syslog 端口映射的方式, 传输到主机上 使用etcd/zookeepr来管理配置变更信息. 本身是key/value的架构

Docker Volume

/var/lib/docker/graph: 存放本地Image里面的分层信息 /var/lib/docker/devicemapper/devicemapper/data: 存储了image和container的二进制数据文件 /var/lib/docker/devicemapper/devicemapper/metadata: 存储了相关的元数据 docker的data文件是一个稀疏磁盘空间, 默认是100G, 实际使用的大小可以使用du来进行查看. 每个容器的大小最大为10G. aufs drvier是docker最早期支持的driver, 是linux内核的一个补丁集. ubuntu会使用 device mapper: 2.6之后引入的, 提供了一种逻辑设备到物理设备的映射框架, 是LVM2的核心. 支持块级别的copy on write特性. VFS: 虚拟文件系统, 不支持COW btrfs: 非常快, 仍然在进化中 高频写操作需要volume: 大量日志文件系统, 数据库系统等 可以使用volume

docker run -it -v /volume rhel7 /bin/bash

使用docker inspect 查看

    "Volumes": {
        "/volume": "/var/lib/docker/volumes/ec3f9aecdffc0818aaec803ca5eccb60506ce3ca4b1bc7e0676
e763733d39ad3/_data"
    },
    "VolumesRW": {
        "/volume": true
    },

也可以使用本机目录挂载到docker容器中去

docker run --rm=true -it -v /storage /volume java /bin/bash
						  本机目录	容器目录

volume的互联, 基于数据容器的单主机互联.

docker run --rm=true --privileges=true --volume-from=32dsdfadsfsf -it rhel7 /bin/bash

docker容器互联

容器间基于link互联, 默认情况下docker允许container互通, 通过-icc=false关闭互通. 一旦关闭互通, 只能通过-link name:alias 命令连接指定container

关闭容器互联

/usr/bin/docker daemon --icc=false --iptables=true

link的隔离是隔离容器端口的. link只能解决一台主机之间的互联.

docker run --rm=true --name=myjavaserver -it java /bin/bash
docker run --rm=true --link=myjavaserver:serverM1 -it java /bin/bash

多台主机的话则无法使用link进行互联. SOCAT是一个多功能的网络工具, 可以用来做简单的HTTP proxy

socat TCP4-LISTEN:6666 TCP4:proxy.company.com:8080

最简单常用的互联方式: 端口映射. 使用docker-proxy

宿主机的0.0.0.0:8080 --> 容器80
docker run -p "8080:80"
docker run --rm=true --name=mysqlserver -p 8066:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_USER=ying -e MYSQL_PASSWORD=nsadm -e MYSQL_DATABASE=testing workstation.pod0.example.com:5000/openshift3/mysql-55-rhel7
docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8066 -container-ip 172.17.0.5 -container-port 3306

可以添加NAT

-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8066 -j DNAT --to-destination 172.17.0.6:3306
-A DOCKER -d 172.17.0.6/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 3306 -j ACCEPT
-A POSTROUTING -s 172.17.0.6/32 -d 172.17.0.6/32 -p tcp -m tcp --dport 3306 -j MASQUERADE

proxy每个port的映射要使用将近11MB的内存. 因此建议直接使用宿主机网络共享出来

docker run --rm=true --name=mysqlserver --net=host -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_USER=ying -e MYSQL_PASSWORD=nsadm -e MYSQL_DATABASE=testing workstation.pod0.example.com:5000/openshift3/mysql-55-rhel7

多个容器公用一个网络, 下面的例子中, 第二个容器使用了第一个容器的IP 地址

docker run --rm=true --name=mysqlserver -e MYSQL_ROOT_PASSWORD=123456 mysql
docker run --rm=true --net=container:mysqlserver java ip addr 

共享一个网络的情况下, 互相访问可以通过localhost来进行彼此访问

docker run --rm=true --net=container:mysqlserver java curl localhost:3306

基于路由的容器互联

首先需要修改容器的IP地址, 这个时候经常会因为linux网桥自身的问题无法成功删除

ifconfig docker0 down 
brctl delbr docker0 

在两台主机上分别执行

route add -net 172.18.0.0/16 gw 172.25.0.9
route add -net 172.17.0.0.16 gw 172.25.0.11
清理防火墙规则
iptables -F; iptables -t nat -F 

网络将会以ovs为趋势, docker官方提出了libnetwork的概念, 还在继续的发展中.

namespace

平行宇宙. 在不同虚拟机中的网卡彼此是不可见的, tap是由软件实现的. veth pari是用于不同network namespace间进行通信的方式, veth pari将一个network namespace数据发往另一个network namespace的veth. 查看容器真正的pid

docker inspect -f '{{.State.Pid}}' [containerID]

创建一个namespace网络的软链接

mkdir -p /var/run/netns
ln -s /proc/1469/ns/net /var/run/netns/1469

此后就可以使用ip netns来进行操作和查看

ip netns ls

查看容器中的ip地址

ip netns exec 1469 ip addr show 

在容器中查看对端的接口

ip netns exec 1469 ethtool -S eth0

在宿主机中查看网络的veth应该与之相对应

安装open vSwitch

yum install openvswitch

添加网桥和gre隧道

ovs-vsctl add-br br0
ovs-vsctl   add-port   br0   gre0   --    set    Interface    gre0    type=gre options:remote_ip=172.25.0.9
brctl addif docker0 br0
ip link set dev br0 up
ip link set dev docker0 up
iptables -t nat -F;iptables -F
ip route add 172.17.0.0/16 dev docker0

ovs-vsctl show

Bridge "br0"
    Port "br0"
        Interface "br0"
            type: internal
    Port "gre0"
        Interface "gre0"
            type: gre
            options: {remote_ip="172.25.0.9"}
ovs_version: "2.3.1-git3282e51"

抓gre的包

tshark -i eth0 ip proto gre

Docker管理工具

Shipyard和cAdvisor 安装shiptyard

OPTIONS= -H=unix:///var/run/docker.sock -H=tcp://0.0.0.0:2375
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock\ shipyard/deploy start 

默认运行在8080端口, 用户名密码是admin/shipyard

cAdvisor是google推出的一个主要用于检测容器状态的工具, 也可以通过容器直接安装

docker run --volume=/:/rootfs:ro --volume=/var/run:/var/run:rw --volume=/sys:/sys:ro --volume=/var/lib/docker/:/var/lib/docker:ro --publish=8082:8082 --detach=true --name=cadvisor google/cadvisor:latest --port=8082

Reigstry

Registry包含一个或多个Repository Repository包含一个或多个Image. Image使用GUID表示, 有一个或多个Tag与之关联

Kubernetes

Docker于CoreOS的恩怨情仇 cgroup最初是由google的工程师提出来, 后来被整合到内核中. Linux容器正式业界一直关注的Google基础设施Borg和Omega的基础之一, 基于之前Google开源的cgroup项目. Borg是一个生产环境的经验下的论文. linux基金会介入coreOS, google, docker等公司的纠纷, 创建了OCP项目 2015年7月22日Google正式对外发布Kubernetes v1.0 CNCF(Cloud Native Computing Foundation) 基金会

Kubernetes重要概念

Namingspace: 关联resource. 资源隔离的一种手段, 不同NS中的资源不能互相访问 Resource: 关联Namespace和ResourceQuta. 集群中的一种资源对象, 处于某个命名空间中. 可以持久化存储到Etcd中, 资源有状态且可以配额. Label: 关联Resouce,Label Selector. 一个Key-value值对. Master节点: 关联工作节点Node. K8s集群的管理节点, 负责集群的管理. 提供集群资源数据访问入口. 负责API Server进程, Controller Manager服务进程. Scheduler服务进程 Node节点: 关联master节点. K8s中的工作节点, 启动和管理k8s中的pod实例. 接受Master节点的管理指令, 运行着一个k8s的守护进程kubelet, 负载均衡器kube-proxy

kubectl describe node kubernetes-minion1

Pod: 关联Node和Serivce: 一组容器的一个"单一集合", K8s中的最小任务调度单元. 一个Pod中的容器共享资源(网络, volume) Service: 关联多个相同Label的Pod, 是一种微服务. 具有一个虚拟访问的地址(IP + Port). Replication Controller: 关联多个相同Label的pod. Pod副本控制器, Volumes: 关联Pod, 是Pod上的存储卷.

Kubernets实践

https://github.com/kubernetes/kubernetes/blob/release-1.0/docs/getting-started-guides/centos/centos_manual_config.md 要保证master/node节点之间可以互相通过域名访问 创建yum源

[virt7-testing]
name=virt7-testing
baseurl=http://cbs.centos.org/repos/virt7-testing/x86_64/os/
gpgcheck=0

安装kubernetes

yum install kubernetes

安装etcd, 他是kubernetes的键值数据库系统

yum install etcd

配置etcd

ETCD_NAME=master
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379,http://0.0.0.0:4001"
ETCD_ADVERTISE_CLIENT_URLS="http://master.example.com:2379,http://master.example.com:4001"

验证状态

etcdctl set testdir/testkey0 1
etcdctl get testdir/testkey0
etcdctl -C http://master.example.com:4001 cluster-health

公用配置/etc/kubernetes/config, 配置master信息

KUBE_MASTER="--master=http://master.pod0.example.com:8080"
KUBE_ETCD_SERVERS="--etcd_servers=http://master:4001"

在Master上面配置/etc/kubernetes/apiserver

KUBE_API_ADDRESS="--address=127.0.0.1"
KUBE_API_ADDRESS="--insecure-bind-address=0.0.0.0"
KUBE_API_PORT="--port=8080"
# KUBE_ETCD_SERVERS="--etcd_servers=http://127.0.0.1:2379"
# 默认ETCD启动在2379端口

在master上启动服务etcd, kube-apiserver, kube-controller-manager, kube-scheduler. etcd是k8s的数据库系统

for SERVICES in etcd kube-apiserver kube-controller-manager kube-scheduler;do systemctl restart $SERVICES; systemctl enable $SERVICES; systemctl status $SERVICES; done

在node节点上进行配置/etc/kubernetes/kubelet

KUBELET_ADDRESS="--address=0.0.0.0"
KUBELET_PORT="--port=10250"
KUBELET_HOSTNAME="--hostname_override=node.pod0.example.com"
KUBELET_API_SERVER="--api_servers=http://master.pod0.example.com:8080"

启动kube-proxy和kubelet服务, 为了让kube-proxy也能找到master需要配置config文件声明master节点位置 在master节点上可以检查node节点的状态是否注册成功

kubectl get nodes
kubectl cluster-info

修改docker配置

OPTIONS='--selinux-enabled=disabled 

在kube-scheduler和kube-controller-manager中添加

After=etcd.service
After=kube-apiserver.service
Requires=etcd.service
Requires=kube-apiserver.service

Kubernetes的版本升级

非常简单, 通过官网下载最新版本的二进制包kubernetes.tar.gz, 解压缩. 停止Master和Node上的服务, 将新版的可执行文件复制到kubernetes的安装目录下.重启服务 kubectl

Available Commands:
  get            Display one or many resources
  describe       Show details of a specific resource or group of resources
  create         Create a resource by filename or stdin
  replace        Replace a resource by filename or stdin.
  patch          Update field(s) of a resource by stdin.
  delete         Delete a resource by filename, stdin, resource and name, or by resources and label selector.
  namespace      SUPERCEDED: Set and view the current Kubernetes namespace
  logs           Print the logs for a container in a pod.
  rolling-update Perform a rolling update of the given ReplicationController.
  scale          Set a new size for a Replication Controller.
  exec           Execute a command in a container.
  port-forward   Forward one or more local ports to a pod.
  proxy          Run a proxy to the Kubernetes API server
  run            Run a particular image on the cluster.
  stop           Gracefully shut down a resource by name or filename.
  expose         Take a replicated application and expose it as Kubernetes Service
  label          Update the labels on a resource
  config         config modifies kubeconfig files
  cluster-info   Display cluster info
  api-versions   Print available API versions.
  version        Print the client and server version information.
  help           Help about any command

查看namespace

kubectl get namespace
kubectl describe namespace default

创建应答式文件nginx.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: master.example.com:5000/nginx
    ports:
    - containerPort: 80
kubectl create -f nginx-pod.yaml

查看events

kubectl get events

搭建本地仓库http://www.cnblogs.com/zhenyuyaodidiao/p/6500950.html

#KUBELET_POD_INFRA_CONTAINER="--pod-infra-container-image=registry.access.redhat.com/rhel7/pod-infrastructure:latest" 

每个Pod里运行着一个特殊的被称之为Pause的容器, 其他容器则为业务容器. 这些业务容器共享Pause容器的网络栈和Volume挂在卷. 官方文档https://docs.docker.com/registry/deploying/

Kubernetes定义

测试镜像文件在https://hub.docker.com/u/kubeguide/ Node信息同步可以通过kube-controller-manager启动参数 --node-sync-period 设置同步的时间周期 Node是自注册的, 当kubelet的启动参数中设置了--register-node为true时, kubelet会向apiserver注册自己. Kubelet进行自注册的启动参数如下: --apiservers=: apiserver的地址 --kubeconfig=: 登录apiserver所需要凭据/证书的目录 --cloud_provider=: 云服务商地址, 用于获取自身的metadata --regiter-node=: 设置为true表示自动注册到apiserver上 通常在容器之间要使用link的方式互联, 但是大量的link会消耗系统资源. 通过Pod的概念可以将多个容器组合在一个虚拟的"主机"内, 可以实现容器之间仅需要通过localhost就能互相通信了. 一个pod中的应用容器共享一组资源 PID命名空间: pod中的不同应用程序可以看到其他应用程序的进程ID 网络命名空间: pod中的多个容器能够访问同一个IP和端口范围. IPC命名空间: Pod中的多个容器能够使用systemV IPC或POSIX消息队列进行通信 UTS命名空间: Pod中的多个容器共享一个主机名 volumes: 共享存储卷, pod中的各个容器可以访问在pod级别定义的volumes

Pod的声明周期是通过Replication Controller来管理的. Pod的生命周期过程包括: 通过模板进行定义, 然后分配到一个Node上运行, 在pod所含容器运行结束后Pod也结束. Pod包含四种状态. Pending: Pod定义正确, 提交到Master Running: Pod已经被分配到某个Node上. Succeeded: Pod中所有容器都成功结束 Failed: Pod中所有容器都结束了.

Label以key/value的形式附加到各种对象上, 如Pod, service, RC, Node. Label Selector分两种, 基于等式的(Equality-based)和基于集合的(Set-Based). 基于等式的Label Selector, name=redis-slave; env != production; 基于集合的Label Selector, name in (redis-master, redis-slave) name not in (php-frontend) Replication Controller通过Label Selector来选择要管理的Pod RC是Kubernetes系统的核心概念, 用于定义Pod副本的数量.

Service可以看做一组提供相同服务的pod对外访问的接口, Service作用于那些Pod是通过Label Selector来定义的. 创建本地仓库, 首先使用阿里加速器https://cr.console.aliyun.com/ pull registry并启动

docker pull docker.io/registry 
docker run -d -p 5000:5000 --name=master.example.com --restart=always --privileged=true  --log-driver=none -v /home/data/registrydata:/tmp/registry registry

k8s的node节点必须要安装一个镜像名为 gcr.io/google_containers/pause-amd64:3.0的镜像. 可以从阿里云下载.

docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0

重新tag镜像名称并且上传到本地registry上

docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0 master.ex
ample.com:5000/google_containers/pause-amd64:3.0
docker push master.example.com:5000/google_containers/pause-amd64:3.0

然后修改docker中的--insecure-registry添加master.example.com:5000

kubernetes部署示例

创建redis-master-controller.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: redis-master
  labels:
    name: redis-master
spec:
  replicas: 1
  selector:
    name: redis-master
  template:
    metadata:
      labels:
        name: redis-master
    spec:
      containers:
      - name: master
        image: master.example.com:5000/kubeguide/redis-master
        ports:
        - containerPort: 6379

创建redis-master-service.yaml 服务, 定义开放的端口和对应的pod

apiVersion: v1
kind: Service
metadata:
  name: redis-master
  labels:
    name: redis-master
spec:
  ports:
  - port: 6379
    targetPort: 6379
  selector:
    name: redis-master

查看services

kubectl get services
NAME           CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes     10.254.0.1       <none>        443/TCP    1d
redis-master   10.254.241.203   <none>        6379/TCP   3m

由于cluster的IP是在创建pod之后由kubernetes自动分配的, 在其他Pod中无法预先知道某个Service的虚拟IP地址. Kubernets则使用Linux环境变量来传达信息. 其他的pod通过REDIS_MASTER_SERVICE_HOST 和 REDIS_MASTER_SERVICE_PORT来获取相关信息. 创建slave的RC

apiVersion: v1
kind: ReplicationController
metadata:
  name: redis-slave
  labels:
    name: redis-slave
spec:
  replicas: 2
  selector:
    name: redis-slave
  template:
    metadata:
      labels:
        name: redis-slave
    spec:
      containers:
      - name: master
        image: master.example.com:5000/kubeguide/guestbook-redis-slave
        env:
        - name: GET_HOSTS_FROM
          value: env
        ports:
        - containerPort: 6379

创建redis-slave-service.yaml 服务文件

apiVersion: v1
kind: Service
metadata:
  name: redis-slave
  labels:
    name: redis-slave
spec:
  ports:
  - port: 6379
  selector:
    name: redis-master

创建fronted-controller.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: frontend
  labels:
    name: frontend
spec:
  replicas: 3
  selector:
    name: frontend
  template:
    metadata:
      labels:
        name: frontend
    spec:
      containers:
      - name: frontend
        image: master.example.com:5000/kubeguide/guestbook-php-frontend
        env:
        - name: GET_HOSTS_FROM
          value: env
        ports:
          - containerPort: 80

配置服务文件fronted-service.yaml. type=NodePort是关键, 表示Node上的物理机端口提供对外访问的能力, 需要注意的是spec.ports.NodePort的端口定义由范围限制, 默认为30000~32767,在此范围之外则会失败

apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    name: frontend
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 30001
  selector:
    name: frontend
tcp    LISTEN     0      128      :::30001                :::*                   users:(("kube-proxy",pid=2831,fd=9))

系统会跟根据pod和service的关系建立相应的endpoint

kubectl get endpoints

Kubernets 对外Service

Service的ClusterIP地址相对于Pod的IP地址来说相对稳定, Service被创建时被分配一个IP地址, 在销毁该Service之前, 这个IP地址不会发生变化. Cluster IP Range池中分配到的IP只能在内部被访问到, 所有其他Pod都可以无障碍的访问到它. 但是如果这个Service作为前端的服务, 则需要这个服务提供公共IP. Kubernetes提供两种对外的Service的type定义. NodePort和LoadBalancer NodePort: 指定spec.type=NodePort, 并指定spec.ports.nodePort的值, 系统就会在Kubernetes集群中的每个Node上打开一个主机上的真实端口号, 这样, 能够访问Node的客户端都能通过这个端口号访问到内部的Service了 LoadBalancer: 如果云服务商支持外接负载均衡器, 则可以通过spec.type=LoadBalancer定义Service. 同时需要指定负载均衡器的IP地址. status.loadBalancer.ingress.ip设置为146.148.47.155为云服务商提供的负载均衡器的IP地址.

apiVersion: v1
kind: Service
metadata: {
  "kind": "Service",
  "apiVersion": "v1",
  "metadata": {
    "name": "my-service"
    },
  "spec":{
    "type": "LoadBalancer",
    "clusterIP": "10.0.171.239",
    "selector": {
      "app": "MyApp"
    },
    "ports": [
      {
        "protocol": "TCP",
        "port": 80,
        "targetPort": 9376,
        "nodePort": 30061,
      },
    ],
  },
  "status": {
    "loadBalancer": {
      "ingress": [
        {
          "ip": "146.148.47.155"
        },
      ]
    }
  }
}

多端口服务

很多情况下, 一个服务都需要对外暴露多个端口号. 防止产生歧义, specports 下面可以定义名字. spec.ports.name="http"

Volume

存储卷是Pod中能够被多个容器访问的共享目录. K8s中的Volume与Pod生命周期相同, 但与容器的生命周期不相关. 当容器终止或者重启时, volume中的数据不丢失. 1) EmptyDir: 一个EmptyDir Volume是在Pod分配到Node时创建的, 从它的名称就可以看出, 它的初始内容为空. 同一个Pod下的所有容器都可以读/写EmptyDir中的相同文件. 当Pod被移除后, EmptyDir中的数据也永久删除. 2) hostPath: 在Pod上挂载宿主机上的文件或目录. 在不同的Node上具有相同配置的Pod可能会因为宿主机上的目录和文件不同而导致对Volume上的目录和文件的访问结果不一致. 一般要使用共享存储 以宿主机/data为存储卷

spec:
  template:
    spec:
      volumes:
      - name: "persistent-storage"
        hostPat:
          path: "/data"
      containers:
        volumeMounts:
          - name: "persistent-storage"
            mountPath: "/data"

3) gcePersistentDisk: 使用google计算引擎上的永久磁盘. 此时要求虚拟机是GCE虚拟机 4) awsElasticBlockStore: 与GCE类似, 该volume是Amazon提供的AWS的EBS volume 5) nfs: 使用NFS提供共享目录挂载到Pod中.

apiVersion: v1
kind: Pod
metadata:
  name: nfs-web
spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
      volumeMounts:
        - name: nfs
          mountPath: "/usr/share/nginx/html"
  volumes:
    - name: nfs
      nfs:
        server: nfs-server.localhost
        path: "/"

6) iscsi: 使用iSCSI存储设备上的目录挂载到Pod中 7) glusterfs 8) rbd 9) gitRepo 10) secret: 通过tmpfs实现的, 这种volume无法持久化 11) persistentVolumeClaim: 从PV(PersistentVolume)中申请所需的空间, PV通常是一种网络存储. 例如NFS, iSCSI, GCE, AWS等等

Namespace

kubernetes集群在启动后, 会创建一个名为default的Namespace

kubectl get namespaces

默认Pod, RC, Service都将系统创建到"default"的Namespace中 用户可以根据创建新的Namespace,

apiVersion: v1
kind: Namespace
metadata:
  name: development

如果不加参数, kubectl get命令将显示属于"default" 命名空间的对象 --namespace 参数可以指定某个命名空间对象 注意: Label是有严格的语法规则的, 但是Annotation是可以随意定义的 Kubelet创建Pod时, 需要通过启动一个名为google_containers/pause的镜像来完成对Pod网络的配置 我之前的实验使用了将google_containers/pause镜像下载到本地的方式来实现的. 需要在每个node上进行操作, 也可以直接在kubelet的配置文件中添加

KUBELET_ARGS="--pod_infra_container_image=master.example.com:5000/google_containers/pause:3.0"

ETCD

etcd是高可用的key/value存储系统, 用于持久化存储集群中. API Server则提供了操作etcd的封装接口API, 以REST方式提供服务, 以REST方式提供服务.

ETCD集群

ETCD配置集群配置/etc/etcd/etcd.conf

[member]
ETCD_NAME=etcd1
ETCD_LISTEN_PEER_URLS="http://10.0.0.1:2380" 		#集群内部使用的IP地址
[cluster]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.0.0.1:2380" 	#广播给集群内其他成员的URL
ETCD_INITIAL_CLUSTER="etcd1=http://10.0.0.1:2380, etcd2=http://10.0.0.2:2380, etcd3=http://10.0.0.3:2380"
ETCD_INITIAL_CLUSTER_STATE="new"		#初始集群状态, new为新建集群
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"	#集群名称
ETCD_ADVERTISE_CLIENT_URLS="http://10.0.0.1:2379"		#广播给外部客户端使用的URL

在etcd2和etcd3加入etcd-cluster集群的实例.

ETCD_INITIAL_CLUSTER_STATE="exsit"

查看集群节点状态

etcdctl cluster-health

查看集群成员列表

etcdctl member list

以kube-apiserver为例, 访问etcd集群的参数设置为

--etcd-servers=http://10.0.0.1:4001, http://10.0.0.2:4001, http://10.0.0.3:4001

Kubernetes 核心原理

资源对象Replication Controller简写为RC,而Replication Controller是指"副本控制器". 创建pod的时候最好不要越过RC直接创建Pod, 因为Replication Controller会通过RC管理Pod副本. 当Pod的重启策略为RestartPolicy=Always时, Replication Controller才会管理该Pod的操作(创建, 销毁, 重启等) Pod实例都会通过RC里定义的Pod模板(template)创建的. selector 指定一个name, 这个name和template中的name对应 对replciationcontroller进行扩大或缩小副本数量

kubectl scale --replicas=3 replicationcontrollers foo

ResourceQuota Controller

Kubernetes 支持三个层次的资源配额管理 1. 容器级别 2. Pod级别, 3. Namespace级别 包括的限制是Pod数量; Replication Controller数量; Service数量; ResourceQuota数量; Secret数量; PV(Persistent Volume) 数量 LimitRanger作用于Pod和Container上, ResourceQuota则作用于Namespace上

ServiceAccount Controller与Token Controller

ServiceAccount Controller与Token Controller是与安全相关的两个控制器. Service Account Controller在Controller manager启动时被创建.

某些特殊场景下, 例如将一个外部数据库作为Service的后端, 或将在另一个集群或Namespae中的服务作为服务的后端. 需要创建一个不带标签选择器的Service. 如果不带标签选择器, 系统不会自动创建Endpoint. 此时需要手动创建一个和该Service同名的Endpoint kube-proxy为每个Service在本地主机上开一个端口(随机选择), 任何访问该端口的连接都被代理到响应的一个后端pod上.

k8s支持两种主要的模式来找到Service. 1). 一个是容器的Service环境变量. 形如{SVCNAME}_SERVICE_HOST. 例如名称为"redis-master"的service, 它对外暴露6379 TCP端口. 且集群IP地址为10.0.0.11. kubelet会为新建的容器添加如下环境变量

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

通过环境变量来找到Service会带来一个不好的结果, 即任何被某个pod所访问的Service, 必须先于该Pod被创建, 否则和这个后创建的Service相关的环境变量, 将不会被加入该Pod容器中.

容器健康检查

Pod通过两类探针来检查容器的健康状态, 一个是LivenessProbe探针. 另一类是使用LivenessProbe探针. LivenessProbe探针有三种实现方式1) ExecAction: 在容器中执行一个命令, 返回值为0, 表明健康 2) TCPSocketAction: 通过容器的IP地址和端口号执行TCP检查 3) HTTPGetAction: 通过容器IP地址和端口号以及路径调用HTTP get方法

livenessProbe:
  exec:
  	command:
  	- cat
  	- /tmp/health
  initialDelaySeconds: 15
  timeoutSeconds: 1
livenessProbe:
  httpGet:
  	path: /heathz
  	port: 8080
  initialDelaySeconds: 15
  timeoutSeconds: 1

安全机制原理

Authenication认证

CA认证在API server上配置三个参数 "--client-ca-file" "--tls-cert-file" 和 "--tls-private-key-file" kuectl 的三个参数"certificate-authority" "client-certificate" 和 "client-key". 或客户端应用的kubeconfig配置文件中的配置项

token认证方式, 添加参数"--token_auth_file=SOMEFILE"

HTTP基本认证方式添加参数"--basic_auth_file=SOMEFILE"

Auhorization授权

在通过API访问资源之前, 必须通过访问策略进行校验. 访问策略通过API Server的启动参数 "--authorization_mode"配置, 该参数包含以下三个值 1) AlwaysDeny 2) AlwaysAllow 3) ABAC

ABAC表示配置使用用户配置的授权策略去管理API Server的请求. HTTP请求包含四个能被授权进程识别的属性 1) 用户名 2) 是否是只读请求 3) 访问的属于那一类资源, 例如Pod 4) 被访问对象所属的Namespace 如果选用ABAC模式, 需要通过API Server的参数选项"--authorization_policy_file=SOME_FILENAME"

# 允许用户alice做任何事情
{"user": "alice"}
# 用户kubelet指定读取资源Pods
{"user": "kubelet", "resource": "pods", "readonl": true} 
# 用户Kubelet能读和写资源events
{"user": "kubelet", "resource": "event"}
# 用户bob只能读取Namespace "myNamespace" 中的资源Pods
{"user": "bob", "resource": "pods", "readonly": true, "ns": "myNamespace"}

Admission Control 插件

SecurityContextDeny: 禁止通过API server管理配置了下列两项配置的pod

spec.containers.securityContext.seLinuxOptions
spec>containers.securityContext.runAsUser

ResourceQuota: 资源配额. --adminssion_control=ResourceQuota, 使插件生效. 对资源对象的最大数量限制

{
    "apiVersion": "v1",
    "kind": "ResourceQuota",
    "metadata": {
        "name": "quota"
    },
    "spec": {
        "hard": {
            "memory": "1Gi",
            "cpu": "20",
            "pods": "10",
            "services": "5",
            "replicationcontrollers": "20",
            "resourcequotas": "1"
        }
    }
}

LimitRanger: 用于列举Namespace中各资源的最小限制, 最大限制和默认值. --adminssion_control=LimitRanger

apiVersion: v1
kind: LimitRange
metadata:
  name: myLimits
spec:
  limits:
  - max:
      cpu: "2"
      memory: 1Gi
    min:
      cpu: 250m
      memory: 6Mi
    type: Pod
  - default:
      cpu: 250m
      memory: 100Mi
    max:
      cpu: "2"
      memory: 1Gi
    min:
      cpu: 250m
      memory: 6Mi
    type: Container

Secret: 私密凭据 登录私有Registry, 第一次登录会创建私有的用户名密码, 相关信息将会写入~/.dockercfg文件中

docker login localhost:5000

Service Account: 多个Secret的集合

kubectl get serviceAccounts

集群安全配置案例

双向认证配置

双向认证配置: 1) 生成根证书, API server服务端证书, 服务器端私钥, 各组件所用的客户端证书和客户端私钥 2) 修改k8s各个服务进程的启动参数, 启动双向认证模式 证书目录/var/run/kubernetes 产生私钥

openssl genrsa -out dd_ca.key 2048

生成根证书(自签证书)

openssl req -x509 -new -nodes -key dd_ca.key -subj "/CN=example.com" -days 5000 -out dd_ca.crt

生成API Server的服务端证书和私钥

openssl genrsa -out dd_server.key 2048
openssl req -new -key dd_server.key -subj "/CN=master.example.com" -out dd_server.csr
openssl x509 -req -in dd_server.csr -CA dd_ca.crt -CAkey dd_ca.key -CAcreateserial -out dd_server.crt -days 5000

生成Controller Manager与Scheduler进程公用的证书和私钥

openssl genrsa -out dd_cs_client.key 2048
openssl req -new -key dd_cs_client.key -subj "/CN=master.example.com" -out dd_cs_client.csr
openssl x509 -req -in dd_cs_client.csr -CA dd_ca.crt -CAkey dd_ca.key -CAcreateserial -out dd_cs_client.crt -days 5000

生成Kubelet所用的客户端证书和私钥, 假设NodeIP地址为192.168.48.142

openssl genrsa -out dd_kubelet_client.key 2048
openssl req -new -key dd_kubelet_client.key -subj "/CN=192.168.48.142" -out dd_kubelet_client.csr
openssl x509 -req -in dd_kubelet_client.csr -CA dd_ca.crt -CAkey dd_ca.key -CAcreateserial -out dd_kubelet_client.crt -days 5000

修改API Server的启动参数/etc/kubernetes/apiserver 并重启apiserver

KUBE_API_ARGS="--log-dir=/var/log/kubernetes --secure-port=443 --client_ca_file=/home/cert/dd_ca.crt --tls-private-key-file=/home/cert/dd_server.key --tls-cert-file=/home/cert/dd_server.crt"

验证api server

curl https://master.example.com:443/api/v1/nodes --cert dd_cs_client.crt --key dd_cs_client.key --cacert dd_ca.crt

修改Controller Manager的启动参数

KUBE_CONTROLLER_MANAGER_ARGS="--log-dir=/var/log/kubernetes --service_account_private_key_file=/home/cert/dd_cs_client.key --root-ca-file=/home/cert/dd_ca.crt --master=https://master.example.com:443 --kubeconfig=/etc/kubernetes/cmkubeconfig"

创建/etc/kubernetes/cmkubeconfig文件, 配置证书相关参数并且重启kube-controller-manager服务

apiVersion: v1
kind: Config
users:
- name: controllermanager
  user:
    client-certificate: /home/cert/dd_cs_client.crt
    client-key: /home/cert/dd_cs_client.key
clusters:
- name: local
  cluster:
    certificate-authority: /home/cert/dd_ca.crt
contexts:
- context:
    cluster: local
    user: controllermanager
  name: my-context
current-context: my-context

在每个Node上创建/var/lib/kubelet/kubeconfig文件.

apiVersion: v1
kind: Config
users:
- name: kubelet
  user:
    client-certificate: /home/dd_kubelet_client.crt
    client-key: /home/dd_kubelet_client.key
clusters:
- name: local
  cluster:
    certificate-authority: /home/dd_ca.crt
contexts:
- context:
    cluster: local
    user: kubelet
  name: my-context
current-context: my-context

修改Kubelet的启动参数, 以修改/etc/kubernetes/kubelet配置文件为例

KUBELET_API_SERVER="--api_servers=https://master.example.com:443"
KUBELET_ARGS="--kubeconfig=/var/lib/kubelet/kubeconfig"

配置kube-proxy, 创建/var/lib/kubeproxy/proxykubeconfig

apiVersion: v1
kind: Config
users:
- name: kubeproxy
  user:
    client-certificate: /home/dd_kubelet_client.crt
    client-key: /home/dd_kubelet_client.key
clusters:
  - name: local
    cluster:
      certificate-authority: /home/dd_ca.crt
contexts:
  - context:
      cluster: local
      user: kubeproxy
    name: my-context
current-context: my-context

配置/etc/kubernetes/proxy并重启kube-proxy

KUBE_PROXY_ARGS="--kubeconfig=/var/lib/kubeproxy/proxykubeconfig --master=https://master.example.com:443"

简单认证配置

创建用户名密码和UID的文件/root/token_auth_file

thomas, thomas, 1
admin, admin, 2

修改API Server的配置, 重启apiserver

KUBE_API_ARGS="--secure-port=443 --token_auth_file=/root/token_auth_file"

使用curl验证API server

curl https://master.example.com:443/version -H "Authorization: Bearer thomas" -k

HTTP base认证

创建用户名密码和UID的文件/root/token_auth_file

thomas, thomas, 1
admin, admin, 2

修改API Server的配置, 重启APIserver

KUBE_API_ARGS="--secure-port=443 --basic_auth_file=/root/basic_auth_file"

用curl验证连接API Server

curl https://master.example.com:443/version --basic -u thomas:thomas -k

 

 

 

Kuberntes 网络原理

每个Pod都有一个独立的IP地址, 所有的Pod都在一个可以直接连通的, 扁平的网络空间中. 不论这些pod是否在同一个宿主机中, 都要求它们可以直接通过对方的IP进行访问. 这种模式称为IP per Pod模型 在同一个pod中的容器可以通过localhost来连接其他容器的端口.

Linux网络栈中引入了网络命名空间(Network Namespace), 这些独立的协议栈被隔离到不同的命名空间中. 彼此间无法通信. Linux的网络命名空间内可以有自己的路由表及独立的Iptables/Netfilter来设置提供包转发, NAT及IP包过滤等功能. 让处在不同命名空间的网络互相通信, 甚至和外部的网络进行通信, 可以使用Veth设备对. 它就像一个管道, 一端连着一个网络命名空间的协议栈, 一端连着另一个网络命名空间的协议栈. 创建一个网络命名空间

ip netns add <name>

查看命名空间中的内容

ip netns exec <name> ip addr show

如果要执行多个命令, 可以先进入命名空间的bash, 使用exit退出命名空间

ip netns exec <name> bash 

veth设备属于可以转移的设备, 即可以在不同命名空间中转换. 但是很多其他设备例如lo设备, vxlan设备, ppp设备, bridge设备等都是不可以转移的.

ip link set veth1 netns ns1

查看是否可以进行转移, 为on则意味着不可以进行转移

ethtool -k docker0|grep netns
netns-local: on [fixed]

veth对, 总是以成对的方式出现的. 两端称为peer 创建Veth设备对:

ip link add veth0 type veth peer name veth1

将veth1迁移到netns ying中

ip link set veth1 netns ying

为veth0/veth1创建ip地址

ip netns exec ying ip addr add 10.1.1.1/24 dev veth1
ip addr add 10.1.1.2/24 dev veth0

启动veth0/veth1

ip link set veth0 up
ip netns exec ying ip link set dev veth1 up

此时两个veth peer就可以彼此通信了. 可以使用ethtool来查看veth对的peer

ethtool -S veth0

网桥

默认MAC地址的过期时间是5min. Linux的网桥提供了这些设备之间互相转发数据的二层设备. 于switch纯二层设备不同, 运行着linux内核的机器本身就是一台主机, 有可能是网络报文的目的地. 其收到的报文除了转发和丢弃, 还可能被送到网络协议栈的上层(网络层), 从而被这台主机的协议栈消化. 所以此时, 网桥即是二层设备也是一个三层设备.

Linux内核是通过一个虚拟的网桥设备(Net Device)来实现网桥的. Net Device与一般的设备不同, 最明显的一个特征是它还可以有自己的一个IP地址.

例如一个br0桥接了两个网卡, eth0, eth1. 对于上层协议栈而言, 只看得到br0, 上层协议栈将报文发送给br0, 网桥设备的处理代码判断报文该发送给eth0还是eth1. 反过来eth0/1接收到的报文被提交给网桥处理代码.

Iptables/Netfilter

Linux网络协议栈中有一组回调函数挂节点. hook钩子函数. Netfilter负责在内核执行各种挂接的规则, 运行在内核模式中. 而iptables是在用户模式下运行的进程, 负责协助维护内核中的Netfilter的各种规则表.

路由

Linux路由表至少包含两个表, 当启用策略路由的时候还会有其他表. 一个是LOCAL一个是MAIN. LOCAL表中包含所有本地设备地址. LOCAL表示在建立网络设备的时候自动创建的, LOCAL表用于供Linux协议栈识别本地地址, 以及进行本地各个不同网络接口之间的数据转发. 查看local表

ip route show table local type local

MAIN表用于各类网络IP地址的转发, 它的建立既可以使用静态配置生成, 也可以使用动态路由发现协议生成. 动态路由发现协议是使用一组组播功能来发送路由发现数据, 动态交换和获取网络的路由信息, 并更新到路由表中. Linux下支持路由发现协议的开源软件有很多, 常用的有Quagga, Zebra等. 路由表查看

ip route list

Docker的网络实现

标准的Docker支持一下四类网络模式 host模式: 使用 --net=host指定 container模式: 使用 --net=container: NAME_or_ID指定 none模式: 使用 --net=none指定 bridge模式: 使用 --net=bridge指定

Kubernets管理模式下, 通常只会使用bridge模式. bridge模式下, 会创建一个docker0的网桥, 然后私有网络空间会给这个网桥设置一个子网. 每一个被创建出来的容器, 都会创建一个虚拟的以太网设备(veth对), 另一端使用Linux的网络命名空间技术, 映射到容器内的eth0设备, 然后从网桥的地址段内给eth0接口分配一个IP地址. 这样做的结果是, 同一个主机上的容器可以彼此通信, 通过IP地址. 但是不同主机上的容器就必须使用port来实现.然后通过这个端口路由或代理到容器上. docker的iptables在nat表中. 前两条是框架, 生效时都会走DOCKER链. NAT第三条的含义是, 若本地发出的数据包不是通往docker0接口时, 即发往主机之外的设备时, 都需要进行动态地址修改MASQUERADE

-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

filter表中, 第二条如果接收到的数据包属于以前已经建立好的连接, 那么允许直接通过. 这样接受到的数据包自然又走回docker0, 并中专到响应的容器.

-A FORWARD -j DOCKER-ISOLATION
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT

创建registry容器后, 自动添加一条路由规则

-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 5000 -j MASQUERADE
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 5000 -j ACCEPT

Kubernetes的网络实现

1) 紧密耦合的容器到容器之间的直接通信 同一个pod内的容器共享同一个网络命名空间, 共享同一个Linux协议栈. 容器之间可以用localhost地址访问彼此的端口. 可以直接使用本地IPC进行通信.

2) 抽象的Pod到Pod之间的通信 同一个Node内的Pod之间的通信, 本身两个pod都是在docker0网段内的IP地址. 直接通过docker0网桥进行中转即可. 不同Node上的Pod之间的通信. k8s对docker0的IP地址进行规划, 保证每一个Node上的docker0地址没有冲突. k8s的网络增强开源软件Flunnel就能够管理资源池的分配. 在GCE环境下, 路由打通是通过GCE来完成的. 在私有云环境中, 需要额外的网络配置.

3) Pod到Service之间的通信 Service是对一组Pod的抽象. 将Service落实的是kube-proxy服务进程. 这个进程可以看做Service的透明代理兼. kube-proxy都会在本地Node上建立一个SocketServer来负责接收请求, 然后均匀发送到后端某个Pod的端口上, 这个过程使用Round Robin负载均衡算法. Services的ClusterIP与NodePort等概念是kube-porxy通过iptables的NAT转换实现的. 访问Service的请求, 不论是cluster IP + TargetPort 的方式, 还是使用节点机 Node IP + NodePort的方式, 都被节点机的iptables规则重定向到kube-proxy监听服务代理端口. 目前kube-proxy只支持RR和会话保持(如果Service中定义了会话亲和性) SocketServer: 端口是随机选择的一个本地空闲端口 kube-proxy在启动和监听到Service或Endpoint的变化后, 会在本机iptables的NAT表中添加4条规则链. KUBE-PORTALS-CONTAINER: 从容器中通过Service Cluster IP和端口号访问Service请求 KUBE-PORTALS-HOST: 从主机中通过Service Cluster IP 和端口号访问Service请求 KUBE-NODEPORT-CONTAINER: 从容器中通过Service的NodePort端口号访问Service的请求 KUBE-NODEPORT-HOST: 从容器中通过Service的NodePort端口号访问Service的请求

4) 集群外部与内部组件之间的通信 Pod被外部访问, 通过NodePort在Node节点上打开一个对外的端口. LoadBalancer.

开源的网络组件

直接路由

可以使用静态路由的方式, 但是静态路由需要每台节点都进行配置. 在每个Node的路由表中添加到对方docker0的路由转发规则配置项. eg. Pod1 docker0 IP子网为10.1.10.0, Node地址为192.168.1.128; Pod2 所在docker0网桥的IP子网是10.1.20.0, Node地址为192.168.1.129 在Node1上用route add命令添加一条到Node2上docker0的静态路由规则

route add -net 10.1.20.0 netmask 255.255.255.0 gw 192.168.1.129

同样, 在Node2上添加一条到Node1上docker0的静态路由规则

route add -net 10.1.10.0 netmask 255.255.255.0 gw 192.168.1.128

使用这种方式, 要保证每台node的docker网段不能重叠. 如果使用在大规模场景中, 则需要添加数以百计的静态路由. 可以借助动态路由发现协议工具Quagga软件来实现.

docker pull index.alauda.cn/georce/router

Quagga容器启动时需要以--privileged特权模式运行, 并且指定--net-host, 表示直接使用物理机的网路

docker run -itd --name-router --privileged --net=host index.alauda.cn/georce/router

过段时间后就会自动添加路由规则

Flannel

它可以协助k8s, 给每一个Node上的Docker容器分配互相不冲突的IP地址. 能在这些IP地址之间建立一个叠加网络. Flannel创建一个flannel0的网桥, 而且这个网桥一端连接docker0网桥, 另一端连接flanneld的服务进程. flanneld进程首先连接etcd, 利用etcd来管理可分配的IP地址段资源. 同时监控etcd中的每个Pod的实际地址, 并在内存中建立一个Pod节点路由表. 然后下连docker0和物理网络, 使用内存中的Pod节点路由表, 将docker0发给它的数据包包装起来, 利用物理网络的连接将数据包投递到目标flanneld上, 从而完成pod到pod的直接通信. Flannel使用了集中的etcd存储, 集中分配和处理IP, 就不会产生冲突. Flannel通过修改docker的启动参数将分配给它的地址段传递进去 --bip. 但是Flannel模型缺省地使用了UDP作为底层传输协议, UDP本身并不可靠.

安装flannel

下载地址 https://github.com/coreos/flannel/releases 将下载的压缩包解压, 把二进制文件flanneld和mk-docker-opts.sh复制到/usr/bin, 既完成了对flannel的安装 编辑服务配置文件/usr/lib/systemd/system/flanneld.service

[Unit]
Description=Flanneld overlay address etcd agent 
After=network.target
Before=docker.service
[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/flanneld
EnviornmentFile=-/etc/sysconfig/docker-network
ExecStart=/usr/bin/flanneld -etcd-endpoints=${FLANNEL_ETCD} $FLANNEL_OPTIONS
[Install]
RequiredBy=docker.service
WantedBy=multi-user.target

编辑文件/etc/sysconfig/flannel,

FLANNEL_ETCD="http://192.168.48.141:4001"
FLANNEL_ETCD_KEY="/atomic.io/network"

在启动flannel之前, 需要在etcd中添加一条网络配置记录, 这个配置将用于flannel分配给每个Docker的虚拟IP地址段

etcdctl set /atomic.io/network/config '{"Network": "10.1.0.0/16"}'

flannel将覆盖docker0网桥, 所以要先停止docker服务. 启动flanneld服务 在每个Node节点上执行以下命令来完成对docker0网桥的配置

/usr/libexec/flannel/mk-docker-opts.sh -i
source /run/flannel/subnet.env
ifconfig docker0 ${FLANNEL_SUBNET}

这样便完成flanne叠加网络的设置

Open vSwitch

Open vSwitch的网桥可以直接建立多种通信通道. 例如GRE/VxLAN. 当容器内的应用访问另一个容器的地址时, 数据包会通过容器内的默认路由发送给docker0网桥. ovs的网桥作为docker0网桥的端口存在, 它会将数据发送给ovs网桥. ovs网络已经通过配置建立了和其他ovs网桥和GRE/VxLAN隧道, 自然能够将数据送达对端的Node 正常启动Open Vswitch后, 会启动两个进程: ovsdb-server与ovs-vswitchd 创建网桥和GRE隧道: 在每个Node上建立ovs的网桥br0, 然后在网桥上创建一个GRE隧道连接对端网桥, 最后把ovs的网桥br0作为一个端口连接到docker0这个linux网桥上(可以理解为交换机互联). 如此一来, 两个节点机器上的docker0网段就能互通了. 创建ovs网桥

ovs-vsctl add-br br0

创建GRE隧道连接对端, remote_ip为对端eth0的网卡地址. remote_ip要设置为对端的IP地址

ovs-vsctl add-port br0 gre1 -- set interface gre1 type=gre option:remote_ip=192.168.18.128

添加br0到本地docker0, 使得容器流量通过OVS流经tunnel

brctl addif docker0 br0

启动br0与docker0网桥

ip link set dev br0 up
ip link set dev docker0 up

添加路由规则, 由于两台Node的docker0网段分别为172.17.43.0/24与172.17.42.0/24, 这两个网段的路由都要经过本机的docker0网桥路由. 需要在每个Node上添加通过docker0网桥转发的172.17.0.0/16端的路由规则. 相当于设置一个大的路由网段涵盖所有Node节点

ip route add 172.17.0.0/16 dev docker0

ping test需要, 清空docker自带iptables规则

iptables -t nat -F; iptables -F

这种网络架构的缺点非常明显, N个Node需要建立N*(N-1)条GRE隧道

k8s网络案例

在k8s上启动一个容器时, 会先启动google_containers/pause这个容器, 其他pod中的容器都会关联到这个容器上. 这个pause的容器执行端口映射规则, 这样就可以简化端口映射的过程. 使用docker port可以查看端口映射

docker port <container>

添加service之后, 所有通往以及从cluster IP出来的流量都被指向kube-proxy的某个随机端口(如果没有设置端口映射) kube-proxy作为一个全功能的代理服务器管理了两个独立的TCP连接: 一个是从容器到kube-proxy,另一个是kube-proxy到负载均衡的目标pod.

K8s开发指南

传统的Web应用大多是B/S架构, 涉及如下规范. 1) 客户-服务器, 这种规范的提出, 改善了用户接口跨多个平台的可移植性. 分离的用户接口和数据存储.使得不同的用户共享相同的数据成为可能 2) 无状态性: 每次request必须包含所有的信息, 可见性: 服务端和客户端不需要了解request的历史. 可靠性: 减少了服务器从局部错误中恢复的任务量. 可伸缩性: 可以很容易的释放资源(无需会话保持). 无状态性会增加每次单独请求的开销 3) 缓存: 客户端缓存response数据的功能, 这样就可以为以后的request公用缓存数据. 但是可能会导致服务器与客户端的数据不一致性.

在传统的B/S架构下, REST提供了统一接口, 分层系统和按需代码. 1) 统一接口: 在REST世界中, 所有的事务都被抽象为资源. REST通过通用的链接器接口对资源进行操作, 极大的解耦. 2) 分层系统: 各分层系统提高了各种层次之间的独立性. 为系统复杂度提供边界. 3) 按需代码: 允许对客户端功能进行扩展.

REST提出了一下的设计准则 1) 网络上的所有事物都被抽象为资源(Resource) 2) 每个资源对应一个唯一的资源标识符(Resource Identifier) 3) 通过通用的链接器接口(Generic Connector Interface)对资源进行操作 4) 对资源的各种操作不会改变资源标识符 5) 所有的操作都是无状态的(Stateless)

API

GET: 获取某一类型的资源列表 POST: 创建一个资源 GET: 获得单个资源 DELETE: 删除单个资源 PUT: 更新或创建资源 PATCH: 选择修改资源详细指定的域

K8s运维管理

Node的隔离:

在kind:node 下添加spec: unschedulable: true

kubectl replace -f unschedule_node.yaml

Node的扩容:

新的Node完成安装后向Master注册即可

Pod动态扩容和缩放

可以使用scale rc的方式来进行操作

kubectl scale rc redis-slave --replicas=3

更新资源对象的Label

可以直接使用kubectl的label指令

kubectl label pod redis-master-bobr0 role=backend

查看响应的Label

kubectl get pods -Lrole

删除一个Label只需要在label后加一个减号即可

kubectl label pod redis-master-bobr0 role-

修改一个Label的值, 需要加上--overwrite参数:

kubectl label pod redis-master-bobr0 role=master --overwrite

将Pod调度到指定Node上

首先给Node打一个特定的标签

kubectl label nodes <node-name> <label-key>=<label-value>
kubectl label nodes node1 zone=north

在spec.template.spec.nodeSelector.zone: north 然后使用create -f 创建 node 然后查看node的分布状态

kubectl get pods -o wide

滚动升级

使用rolling-update命令, 创建一个新的RC, 然后自动控制旧的RC中的Pod副本数量逐渐减少到0.但是新旧的RC必须处在同一个Namespace中.

kubectl rolling-update redis-master -f redis-master-controller-v2.yaml

或者也可以直接指定镜像源进行滚动升级

kubectl rolling-update redis-master --image=redis-master:2.0

如果更新过程中发现配置有误, 可以终端操作, 并使用--rollback选项进行回滚

kubectl rolling-update redis-master --image=kubeguide/redis-master:2.0 --rollback

Kubernetes的高可用方案

etcd要配置集群参考etcd的配置部分

Kubernetes Master组件的高可用方案

至少使用三台服务器安装Master服务, 并且使用Active-Standby-Standby模式, 保证任何时候总有一套Master能够正常工作. 可以使用pacemaker等工具来实现.

k8s资源配额管理

--adminssion_control=LimiRanger, ResourceQuota spec.template.spec.name.resources.limits.cpu:0.5 spec.template.spec.name.resources.limits.memory: 128Mi cpu的0.5也可以写成500m. k8s启动一个容器时, 会将CPU的配置值乘以1024并转为整数传递给docker run的--cpu-shares参数, 之所以乘以1024是因为Docker的cpu-shares参数是以1024为技术计算CPU时间的. 这个计数仅仅是一个权重, 使用cpu时间等于权重*(所有容器权重和) 再乘以CPU核心数.

apiVersion: v1
kind: LimitRange
metadata:
  name: limit-range-1
spec:
  limits:
    - type: "Pod"
      max:
        cpu: "2"
        memory: 1Gi
      min:
        cpu: 250m
        memory: 32Mi
    - type: "Container"
      max:
        cpu: "2"
        memory: 1Gi
      min:
        cpu: 250m
        memory: 32Mi
      default:
        cpu: 250m
        memory: 64Mi

查看限额配置

kubectl describe limits

使用ResourceQuota可以实现基于租户的配额管理. 不同租户使用不同的命名空间 创建命名空间

apiVersion: v1
kind: Namespace
metadata:
  name: development

创建ResourceQuota配额配置

apiVersion: v1
kind: ResourceQuota
metadata:
  name: quota-development
  namespace: development
spec:
  hard:
    cpu: "32"
    memory: 256Gi
    persistentvolumeclaims: "10"
    pods: "100"
    replicationcontrollers: "50"
    resourcequotas: "1"
    secrets: "20"
    services: "50"

查看ResourceQuota的详细信息

kubectl describe quota quota-development --namespace=development

可以理解为ResouceQuota是对总额的限定, Limitrange是对个别单位的限定

K8s监控

cAdvisor

开源软件cAdvisor用于监控容器运行状态的利器. 默认已经安装在kubelet组件中了, kubelet的启动参数 --cadviosr-port定义了cAdvisor对外提供服务的端口号, 默认为4194 cAdvisor也可以使用api来获取主机的相关信息 http://master.example.com:4194/api/v1.3/machine 返回一个json文件 查看容器节点上最近一分钟内的性能数据

http://192.168.48.142:4194/api/v1.3/subcontainers/system.slice/docker-443bcc6793b26150bffd8ab00ac803309f0581b8470c7214763e10b40c08350f.scope

容器日志

可以查看资源的event日志. 使用kubectl describe pods 可以看到event信息, 对于troubleshooting非常有效 查看日志内容可以使用kubectl logs

kubectl logs <pod_name>
kubectl logs <pod_name> -c <container_name>

相当于容器的命令

docker logs <container_id>

查看kubernetes的系统日志

journalctl -u kube-controller-manager

单独指定日志存放目录: --log-dir=/var/log/kubernetes

K8s社区

https://github.com/GoogleCloudPlatform/kubernetes/wiki/User-FAQ https://github.com/GoogleCloudPlatform/kubernetes/wiki/Debugging-FAQ

kubernetes DNS服务配置

Pod在访问其他Service时, 可以通过两种服务发现方式来完成, 即环境变量和DNS的方式. 使用环境变量是有限制条件的, 即Service必须在Pod之前被创建出来, 然后系统才能在新建的Pod中自动设置与Service相关的环境变量. DNS则没有这个限制

K8s提供的DNS由以下三个组件组成 1) etcd: DNS存储 2) kube2sky: 将Kubernetes Master中的Service注册到etcd 3) skyDNS: 提供DNS域名解析服务 这三个组件都可以以Pod的方式启动和运行, 在K8s集群中需要将各个Pod之间的网络打通. 创建skydns-rc2.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: kube-dns-v6
  namespace: default
  labels:
    k8s-app: kube-dns
    version: v6
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 1
  selector:
    k8s-app: kube-dns
    version: v6
  template:
    metadata:
      labels:
        k8s-app: kube-dns
        version: v6
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - name: etcd
        image: gcr.io/google_containers/etcd:2.0.9
        command:
        - /usr/local/bin/etcd
        - -listen-client-urls
        - http://0.0.0.0:2379,http://0.0.0.0:4001
        - -advertise-client-urls
        - http://127.0.0.1:2379,http://127.0.0.1:4001
        - -initial-cluster-token
        - skydns-etcd
      - name: kube2sky
        image: gcr.io/google_containers/kube2sky:1.11
        resources:
          limits:
            cpu: 100m
            memory: 50Mi
        command:
        - /kube2sky
        - --kube_master_url=http://10.8.65.48:8080
        - -domain=kube.local
      - name: skydns
        image: gcr.io/google_containers/skydns:2015-03-11-001
        resources:
        command:
        - /skydns
        - -machines=http://localhost:4001
        - -addr=0.0.0.0:53
        - -domain=kube.local.
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
      dnsPolicy: Default

创建skydns-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: default
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "KubeDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP: 10.254.0.10
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP

启动DNSserver的cluster启动ip是10.254.0.10 修改每台Node上Kubelet的启动参数 --cluster_dns=10.254.0.10 --cluster_domain=cluster.local

kube2sky容器应用通过调用kubernetes master的API获得集群中所有Service的信息, 并持续监控新Service的生成. 然后写入etcd中

kubectl exec kube-dns-v6-5tpm2 -c etcd --namespace=default etcdctl ls /skydns/kube/local

在域名下kube.local的域名下可以查看各服务对应的键值, 可以看到完整的域名解析redis-master.default.kube.local

kubectl exec kube-dns-v6-5tpm2 -c etcd --namespace=kube-system etcdctl get /skydns/local/kube/local/default/redis-master

在每个Pod中的/etc/resolv.conf文件中添加了DNS域名解析

nameserver 10.254.0.10
search default.svc.kube.local svc.kube.local kube.local localdomain

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

test

 

posted on 2017-03-22 07:51  孙大喜  阅读(874)  评论(0编辑  收藏  举报