Kubernetes 1.6到1.7升级记录

最近正在制定将团队生产环境的Kubernetes集群从1.6升级1.7的计划。 Kubernetes 1.8已经发布,所以准备考虑从1.6到1.7的升级。

准备

当前1.7的最新版本是1.7.8。在做准备之前需要仔细读一遍官方的Kubernetes 1.7 (https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.7.md#action-required-before-upgrading)。

使用ansible升级k8s的核心组件

目前我们总共有两个高可用的Kubernetes集群,分别是测试环境和生产环境,版本都是1.6.10。 这两套环境的Kubernetes集群都是基于ansible自动部署,在1.6.x的每个小版本的升级也都是使用ansible完成。

这里准备的升级步骤是本地VM环境 => 测试环境 => 生产环境。 先使用ansible在本地VM中部署k8s 1.6.10的集群,然后再使用ansible将本地1.6升级到1.7并做一些验证,再升级测试环境,测试环境稳定运行一段时间后完成生产环境的升级。

本地VM环境中的ansible play的十分顺利,集群的各个核心组件已经成功的升级到1.7.8。

以容器形式运行组件的升级

接下来要对一些以容器形式运行的组件升级,对应版本如下:

  • flannel 0.9.0
  • kube-dns 1.14.5
  • dashboard 1.7.1

升级kube-dns的注意事项

kube-dns 1.14.5部署文件的地址在gitlab中发生了变化,在内容上使用Deployment替换了Replication Controller。

wget https://raw.githubusercontent.com/kubernetes/kubernetes/e1d6bcc22736a15ce662b3bd1009a16cdde5cd86/cluster/addons/dns/kube-dns.yaml.base
wget https://raw.githubusercontent.com/kubernetes/kubernetes/e1d6bcc22736a15ce662b3bd1009a16cdde5cd86/cluster/addons/dns/transforms2sed.sed

查看transforms2sed.sed:

s/__PILLAR__DNS__SERVER__/$DNS_SERVER_IP/g
s/__PILLAR__DNS__DOMAIN__/$DNS_DOMAIN/g
s/__MACHINE_GENERATED_WARNING__/Warning: This is a file generated from the base underscore template file: __SOURCE_FILENAME__/g

将$DNS_SERVER_IP替换成10.96.0.10,将DNS_DOMAIN替换成cluster.local。 注意$DNS_SERVER_IP要和kubelet设置的–cluster-dns参数一致

执行:

cd ~/k8s/kube-dns
sed -f transforms2sed.sed kube-dns.yaml.base > kube-dns.yaml
  • 上面的变量DNS_SERVER要和kubelet设置的–cluster-dns参数一致。
kubectl create -f kube-dns.yaml

查看kube-dns的Pod,确认所有Pod都处于Running状态:

kubectl get pods --all-namespaces
NAMESPACE     NAME                                    READY     STATUS    RESTARTS   AGE
kube-system   kube-dns-3468831164-chjw5               3/3       Running   0          3m

测试一下DNS功能是否好用:

kubectl run curl --image=radial/busyboxplus:curl -i --tty

nslookup kubernetes.default
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes
Address 1: 10.96.0.1 kubernetes.default.svc.cluster.local

kube-dns是Kubernetes实现服务发现的重要组件之一,默认情况下只会创建一个DNS Pod,在生产环境中我们可能需要对kube-dns进行扩容。 有两种方式:

  • 手动扩容 kubectl –namespace=kube-system scale deployment kube-dns –replicas=

  • 使用DNS Horizontal Autoscaler

升级dashboard的注意事项

因为之前部署Dashboard 1.6时创建了ServiceAccount kubernetes-dashboard,并分配了cluster-admin的权限。 所以在升级前应该先删除掉这个ServiceAccount:

kubectl delete  serviceaccount kubernetes-dashboard -n kube-system
kubectl delete  clusterrolebinding kubernetes-dashboard

1.7.x版本的dashboard对安全做了增强,默认需要以https的方式访问,增加了登录的页面,同时增加了一个gcr.io/google_containers/kubernetes-dashboard-init-amd64的init容器。

另外需要注意dashboard调整了部署文件的源码目录结构:

mkdir -p ~/k8s/
wget https://raw.githubusercontent.com/kubernetes/dashboard/b44f7cc5fde4dbe2a884b1e32a2b363d8031e4ca/src/deploy/recommended/kubernetes-dashboard.yaml
kubectl create -f kubernetes-dashboard.yaml

kubernetes-dashboard.yaml文件中的ServiceAccount kubernetes-dashboard只有相对较小的权限,因此我们创建一个kubernetes-dashboard-admin的ServiceAccount并授予集群admin的权限,创建kubernetes-dashboard-admin.rbac.yaml:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-admin
  namespace: kube-system

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard-admin
  labels:
    k8s-app: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard-admin
  namespace: kube-system
kubectl create -f kubernetes-dashboard-admin.rbac.yaml
serviceaccount "kubernetes-dashboard-admin" created
clusterrolebinding "kubernetes-dashboard-admin" created

查看kubernete-dashboard-admin的token:

kubectl -n kube-system get secret | grep kubernetes-dashboard-admin
kubernetes-dashboard-admin-token-mrngz   kubernetes.io/service-account-token   3         18m

kubectl describe -n kube-system secret kubernetes-dashboard-admin-token-mrngz
Name:           kubernetes-dashboard-admin-token-mrngz
Namespace:      kube-system
Labels:         <none>
Annotations:    kubernetes.io/service-account.name=kubernetes-dashboard-admin
                kubernetes.io/service-account.uid=906d3aa6-b06d-11e7-afdd-080027d9d784

Type:   kubernetes.io/service-account-token

Data
====
ca.crt:         1302 bytes
namespace:      11 bytes
token:          ......

在dashboard的登录窗口使用上面的token登录。

使用Docker和Kubernetes构建可伸缩的微服务

从现在开始,我们将从更高的维度讨论微服务,涵盖了组织敏捷性、设计和依赖的思考、领域驱动设计以及Promise理论。当我们深入使用之前介绍的三个流行的微服务框架:Spring Boot、Dropwizard和WildFly Swarm,我们能够使用它们开箱即用的能力去构建一个暴露或者消费REST服务的应用,能够使用外部环境对应用进行配置,可以打包成一个可执行的jar,同时提供Metrics信息,但这些都是围绕着一个微服务实例。当我们需要管理微服务之间的依赖、集群的启动和关闭、健康检查以及负载均衡的时候,我们使用微服务架构会面临什么问题呢?本章,我们将讨论这些高阶话题用来理解部署微服务时面对的挑战。

当我们开始将我们的应用和服务按照微服务的思路进行拆分后,我们将面临这样的场景:我们有了更多的服务、更多的二进制内容、更多的配置,更多的交互点等等。传统方式是将这些构建成一个二进制单元,比如:WARs或者EARs,然后将其打包后等待运维人员将它部署到我们指定的应用服务器上。如果对于高可用有要求,也会将应用服务器进行分布式部署,形成集群,依靠负载均衡、共享磁盘(数据库)等方式提升可用性。传统运维体系下也开发了一些自动化部署的工具,比如:Chef和Ansible,工具虽然简化了部署,但是开发人员还是需要面对部署时容易出现的问题,比如:配置、环境等不可预知的问题。
  • chef
    Chef是由Ruby与Erlang写成的配置管理软件,它以一种纯Ruby的领域专用语言(DSL)保存系统配置“烹饪法(recipes)”或“食谱(cookbooks)”。Chef由Opscode公司开发,并在Apache协议版本2.0下开源发布。

  • ansible
    使用python构建,中文化资料比较多,Ansible的简洁界面和可用性非常迎合系统管理员的想法。

在传统方式下尝试微服务的部署,将会是个糟糕的结果。如何解决应用服务器在开发、测试以及生产环境的不同配置?如果没有,如何能够捕获到这些配置的变更?而这些变更如何确认已经运行在应用服务器中了?运行时软件环境,比如:操作系统、JVM以及相关的组件在生产和开发环境下的不同问题如何解决?如果我们的应用已经针对特定的JVM做了调优,这些调优参数会不会影响到他人?如果部署微服务,你会选择使用进程隔离的方式将它们部署在一台机器上吗?如果其中一个微服务实例消耗了系统100%的资源,该如何是好?如果过度的占用了I/O或者共享存储怎么办?如果部署了多个微服务实例的宿主机崩溃了怎么办?我们的应用为此做过应对方案吗?将应用分拆为微服务是小,但面对的问题显然会更多。

不可变的递交

不可变的递交(Immutable delivery)原则可以帮助我们应对上述的部分问题,在这个体系下,我们将使用镜像技术来尝试减少开发到生产的步骤。例如:构建系统能够输出一个包含了操作系统、JVM、配置、相关组件的镜像,我们可以将它部署到一个环境中,测试它,如果通过测试,最终可以将它部署到生产环境中而不用担心开发流程使交付的软件缺少了什么。如果你想变更应用,那么可以回到刚才这个流程的最开始,应用你的修改,重新构建镜像,最终完成部署,如果出乎你的意料,程序有问题,你可以直接选择回滚到上一个正确的镜像而不用担心遗漏了什么。

这听起来很好,但是我们怎么做到呢?将应用打包成一个jar还不足以足够做到这些。JVM是底层实现,我们如何将它也打包进去,而JVM又使用了操作系统级别组件,这些内容我们都要打包,除此之外,我们还需要配置、环境变量、权限等等,这些都需要打包,而这些内容无法被打包到一个可执行jar中去。更加重要的是,不止java一种微服务,如果程序使用NodeJS、Golang编写,我们还要针对不同的语言环境做不同的打包。你可能想使用自动化手段完成这些软件的安装,将基础设施作为服务(IaaS),使用它们的API完成环境的搭建。事实上Netflix已经使用了自动化构建工具来完成VM的构建,并利用这项技术实现了不可变的递交,但是VM很难管理、更新和变更,而每个VM都有自己完备的虚拟化环境,对资源有些浪费。

那么有什么更加轻量化的打包和镜像化方式让我们使用吗?

Docker,Docker,Docker

Docker是近几年出现用于解决不可变递交的优雅解决方案,它允许我们将应用以及应用的所有依赖(包括了:OS,JVM以及其他组件)打包成为一个轻量的、分层的镜像格式。然后Docker使用这些镜像,运行它们,产生实例,而这些实例都运行在Linux containers中,在Linux containers中,会带来CPU、内存、网络以及磁盘的隔离。在这种模式下,这些容器实例就是一种应用虚拟化的方式,它运行一个进程去执行,你甚至可以在实例中运行ps查看你的进程,而且这个容器实例具备访问CPU、内存、磁盘和网络的能力,但是它只能使用指定好的配额。例如:能够启动一个Docker容器,只为它分配一部分的CPU、内存以及I/O的访问限制。如果在Linux containers外部去看,在主机上,这个容器就是一个进程,不需要设备驱动的虚拟化、操作系统、网络栈以及特殊的中间层,它仅仅是一个进程。这意味着,我们可以在一台机器上部署尽可能多的容器,提供了比虚拟机更高的部署密度。

在这些激动人心的特性下,其实没有革命性的技术。Docker使用到的技术有:cgroups、namespaces以及chroot,这些都已经在Linux内核中运行了相当长的时间,而这些技术被Docker用来构造应用虚拟化技术。Linux containers已经推出了十几年,而进程虚拟化技术在Solaris和FreeBSD上出现的时间更早。以往使用这些技术的lxc会比较复杂,而Docker通过简单的API以及优秀的用户体验使得Linux containers的运用变得火热起来,Docker通过一个客户端命令工具能够与Linux containers进行交互,去部署Docker镜像,而Docker镜像的出现改变了我们打包和交付软件的方式。

一旦你拥有了镜像,可以迅速的转化为Linux containers,镜像是按照层进行构建的,一般会在一个基础的层(例如:RHEL、 Debian等)上进行构建,然后包含应用所需的内容,构建应用其实也就是在基础层上进行一层一层的镜像构建。镜像的出现,是的发布到各种环境变得容易,不会在面对一堆零散的内容,如果发现基础镜像中有问题,可以进行重新构建,其他镜像进行重新选择构建即可,这使得从开发环境到测试,再到生产环境减少了人工干预发布内容的环节,如果我们要新发布一版本,只需要重新构建一个镜像即可,而改动只是去修改了镜像中对应的层。

构建了镜像,但是我们怎样启动一个应用?怎样停止它?怎样做健康检查?怎样收集应用的日志、Metrics等信息,使用标准的API可以使我们自己构建工具来完成这些工作。出色的集群机制,例如服务发现、负载均衡、失败容错以及配置使得开发人员很容易获得这些特性。

Docker相关的技术可以关注 https://www.gitbook.com/book/weipeng2k/the-docker-book/details

Kubernetes

外界都知晓Google使用Linux containers技术来支撑其扩展性,事实上Google的所有应用都运行在Linux containers上,并且被他们的管理系统Brog进行着管理。前Google工程师Joe Beda说,公司每周要启动超过20亿次的容器,Google甚至投入资源涉及到linux底层技术来支持其容器在生产环境的运用。在2006年,Google开始了一个名叫 进程容器 的项目,最终演变成为了cgroups,而它在2008被合并到了Linux核心,同年正式发布。Google在拥有极强的运维容器的背景下,其对构建容器平台的影响力就不言而喻了,事实上,一些流行的容器管理项目都受到了Google的影响。
  • Cloud Foundry
    它的创立者Derek Collison和Vadim Spivak都在Google工作过,并且使用Borg系统很多年

  • Apache Mesos
    它的创立者Ben Hindman在Google实习过,与Google的诸多工程师有过容器技术的交流(围绕容器集群、调度和管理等技术)

  • Kubernetes
    开源的容器集群管理平台和社区,创建它的工程师,同时也在Google创建了Borg

    在Docker震惊技术届的2013年,Google决定是时候开源他们下一代的技术–Borg,而它被命名为Kubernetes。今天,Kubernetes是一个巨大、开放和快速成长的社区,来自Google、Red Hat、CoreOS以及其他的个体在为它做出贡献。Kubernetes为在可伸缩的Linux containers下运行微服务提供了非常多有价值的功能,Google将近20年的运维经验都浓缩到了Kubernetes,这对我们使用微服务部署产生了巨大的影响。大部分高流量的互联网企业在这个领域耕耘了很长时间(Netflix、Amazon等)尝试构建的伸缩技术,在Kubernetes中都已经默认进行了集成,在正式深入例子之前,我们先介绍一些Kubernetes的概念,接下来在后面的章节将会用它来挂历一个微服务集群。

Pods

一个Pod是一个或者多个Docker容器的组合,一般情况下一个Pod对应一个Docker容器,应用部署在其中。

Kubernetes进行编排、调度以及管理Pod,当我们谈到一个运行在Kubernetes中的应用时,指的是运行在Pod中的Docker容器。一个Pod有自己的IP地址,所有运行在这个Pod中的容器共享这个IP(这个不同于普通的Docker容器,普通的Docker容器每个实例都有一个IP),当一个卷挂载到Pod,这个卷也能够被Pod中的容器共同访问。

关于Pod需要注意的一点是:它们是短暂的,这代表着它们会在任何时候消失(不是因为服务崩溃就是集群cluster杀死了它),它们不像VM一样引起你的额外注意。Pods能够在任意时刻被销毁,而这种意外的失败就如同介绍微服务架构中任何事情都会失败一样(design for failure),我们强烈建议在编写微服务时时刻记着这个建议。和之前介绍的其他原则相比,这个建议显得更加重要。

Kubernetes的最小部署单元是Pod而不是容器。作为First class API公民,Pods能被创建,调度和管理。简单地来说,像一个豌豆荚中的豌豆一样,一个Pod中的应用容器同享同一个上下文(比如:PID名字空间、网络等)。在实际使用时,我们一般不直接创建Pods, 我们通过replication controller来负责Pods的创建,复制,监控和销毁。一个Pod可以包括多个容器,他们直接往往相互协作完成一个应用功能。

标签(Label)

标签(Label)是一个能分配给Pods的简单键值对,比如:release=stable或者tier=backend,Pods(或者其他资源,但是我们当前只关注Pods)可以拥有多个标签并且可以以松耦合的方式进行分组,这在Kubernetes的使用过程中非常常见。因此一点也不奇怪,Google使用这种简单的方式用来区分不同的容器,并以此来构建大规模伸缩的集群。当我们用标签区分了Pods之后,我们可以使用 label selector 来按照分组来查询所有的Pods,例如:如果我们有一些Pods打上了tier=backend的标签,而其他的一些打上了tier=frontend标签,只需要使用 label selector 表达式 tier != frontend就可以完成对所有没有打上tier=frontend的Pods进行查询,而 label selector 在接下来介绍的 replication controllers 和 services 所使用。

复制控制器(Replication Controllers)

当我们讨论微服务的可伸缩性时,可能想的是将给定的一组微服务部署到多个实例(机器)上,用多个实例的部署来增加伸缩性。Kubernetes为伸缩性定义了一个叫做 Replication Controllers 的概念,它能够管理给定的一组微服务的多个复制体(replicas),例如:我们需要管理许多打上 tier=backend and release=stable 的需要Pods,可以创建一个复制控制器,该控制器拥有对应的 label selector ,此时它就能够在集群中以replicas的形式控制和管理这些Pods。如果我们设置replica的数量为10,当Kubernetes会确定当前的复制控制器是否达到了该状态,如果此刻只有5个,那么Kubernetes就会循环创建剩余的5个,当有20个运行着,Kubernetes将会选择停止10个。Kubernetes将会尽可能的保持设定的10个replica的状态,你可以认为使用复制控制器来控制集群的数量是非常容易的事情,在接下来的章节中,我们会看到使用复制控制器的例子。

服务(Services)

我们最后需要理解的Kubernetes概念是服务(Service), Replication Controllers 能控制一个服务下的多个复制体(replicas),我们也观察到Pods能够被停止(要么自己crash、或者被kill,也有可能被复制控制器停止),因此,当我们尝试与一组Pods进行通信时,不应该依赖于具体的IP(每个Pod都有自己的IP),我们需要的是能够以组的形式访问这些Pods的方式,以组的形式发现它们,可能的话能够以负载均衡的方式访问它们,这个就是 服务(Service) 需要做的。它(服务)允许我们通过一个 label selector 获取一组Pods,将它们抽象为一个虚拟IP,然后以这个虚拟IP来让我们对这些Pods进行发现和交互,我们将在接下来的章节中介绍具体的例子。

Service是定义一系列Pod以及访问这些Pod的策略的一层抽象。Service通过Label找到Pod组。因为Service是抽象的,所以在图表里通常看不到它们的存在

了解这些简单的概念,Pods、Labels、Replication Controllers和services,我们能够以可伸缩的模式,用Google的实践,来管理微服务。这些实践花费了多年,经历了多次失败总结出来的经验之谈,而这个模式能够解决复杂的问题,因此强烈建议学习这些概念以及实践,使用Kubernetes来管理你的微服务。

开始使用Kubernetes

Docker和Kubernetes都是基于Linux本地技术的产品,因此它们需要运行在一个基于Linux的环境中,我们假设大部分的Java开发人员都是工作在Windows或者Mac下,我们推荐在Linux环境下进行相关的实践。

接下来的内容,作者作为redhat的员工,开始介绍CDK(RedHat Container Development Kit),然后是CDK的安装,译者觉得CDK没有多大的参考性,因此将其替换成了对Kubernetes官方的MiniKube使用,并基于MiniKube在linux机器上搭建Kubernetes。

Kubernetes之MiniKube的安装

笔者准备了aliyun oss 下载,比googleapis快许多

该文档介绍如何运行起一个本地Kubernetes集群,需要一个支持Hyper-V虚拟化的CPU以及至少8GB CPU

笔者的环境是 ubuntu 16.04 / amd k8 4 core CPU / 16 gb mem

需要提前安装VirtualBox5.1,自行到官网上进行安装,不要图简单使用ubuntu默认的,那个平常自己使没问题,但是MiniKube不行

安装MiniKube

wget http://029145.oss-cn-hangzhou.aliyuncs.com/minikube-linux-amd64 mv minikube-linux-amd64 minikube chmod u+x minikube sudo mv minikube /usr/local/bin/ 

安装Kubectl

wget http://029145.oss-cn-hangzhou.aliyuncs.com/kubectl chmod u+x kubectl sudo mv kubectl /usr/local/bin/ 

启动MiniKube

通过以下命令启动minikube,该过程会下载一个ISO镜像,然后完成启动。

minikube start 

下载依赖的镜像

这个过程最为复杂,当启动minikube时,会自动下载一些镜像,但是这些镜像都被墙了,但是我们可以从aliyun的仓库下载对应的镜像,然后将其重命名。在启动完minikube后,使用minikube ssh可以登录到后台,然后运行下面的命令完成镜像的下载和别名设置。

docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0 docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0 gcr.io/google_containers/pause-amd64:3.0  docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kube-addon-manager-amd64:v6.1 docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kube-addon-manager-amd64:v6.1 gcr.io/google-containers/kube-addon-manager:v6.1  docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kubedns-amd64:1.9 docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kubedns-amd64:1.9 gcr.io/google_containers/kubedns-amd64:1.9  docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kube-dnsmasq-amd64:1.4 docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kube-dnsmasq-amd64:1.4 gcr.io/google_containers/kube-dnsmasq-amd64:1.4  docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/exechealthz-amd64:1.2 docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/exechealthz-amd64:1.2 gcr.io/google_containers/exechealthz-amd64:1.2  docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/kubernetes-dashboard-amd64:v1.5.0 docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/kubernetes-dashboard-amd64:v1.5.0 gcr.io/google_containers/kubernetes-dashboard-amd64:v1.5.1 

测试echoserver

运行命令创建一个echoserver服务,运行如下命令:

kubectl run hello-minikube --image=registry.cn-hangzhou.aliyuncs.com/google-containers/echoserver:1.4 --port=8080 kubectl expose deployment hello-minikube 

然后运行minikube service hello-minikube –url,将会返回hello-minikube的url,然后可以基于该url做一下测试。

$ minikube service hello-minikube --url http://192.168.99.100:31907 $ curl http://192.168.99.100:31907/123 CLIENT VALUES: client_address=172.17.0.1 command=GET real path=/123 query=nil request_version=1.1 request_uri=http://192.168.99.100:8080/123  SERVER VALUES: server_version=nginx: 1.10.0 - lua: 10001  HEADERS RECEIVED: accept=*/* host=192.168.99.100:31907 user-agent=curl/7.47.0 BODY: -no body in request- 

可以看到请求对应的url,有数据返回,当然也可以启动dashboard,比如运行minikube dashboard将会打开管理页面。

Kubernetes 1.8 集群搭建

目前 Kubernetes 1.8.0 已经发布,1.8.0增加了很多新特性,比如 kube-proxy 组建的 ipvs 模式等,同时 RBAC 授权也做了一些调整,国庆没事干,所以试了一下;以下记录了 Kubernetes 1.8.0 的搭建过程。

一、环境准备

目前测试为 5 台虚拟机,etcd、kubernetes 全部采用 rpm 安装,使用 systemd 来做管理,网络组件采用 calico,Master 实现了 HA;基本环境如下

未分类

本文尽量以实际操作为主,因为写过一篇 Kubernetes 1.7 搭建文档(https://mritd.me/2017/07/21/set-up-kubernetes-ha-cluster-by-binary/),所以以下细节部分不在详细阐述,不懂得可以参考上一篇文章;本文所有安装工具均已打包上传到了 百度云(https://pan.baidu.com/s/1nvwZCfv) 密码: 4zaz,可直接下载重复搭建过程,搭建前请自行 load 好 images 目录下的相关 docker 镜像

二、搭建 Etcd 集群

2.1 生成 Etcd 证书

同样证书工具仍使用的是 cfssl,百度云的压缩包里已经包含了,下面直接上配置(注意,所有证书生成只需要在任意一台主机上生成一遍即可,我这里在 Master 上操作的)

etcd-csr.json

{
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "O": "etcd",
      "OU": "etcd Security",
      "L": "Beijing",
      "ST": "Beijing",
      "C": "CN"
    }
  ],
  "CN": "etcd",
  "hosts": [
    "127.0.0.1",
    "localhost",
    "10.10.1.5",
    "10.10.1.6",
    "10.10.1.7",
    "10.10.1.8",
    "10.10.1.9"
  ]
}

etcd-gencert.json

{
  "signing": {
    "default": {
        "usages": [
          "signing",
          "key encipherment",
          "server auth",
          "client auth"
        ],
        "expiry": "87600h"
    }
  }
}

etcd-root-ca-csr.json

{
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "O": "etcd",
      "OU": "etcd Security",
      "L": "Beijing",
      "ST": "Beijing",
      "C": "CN"
    }
  ],
  "CN": "etcd-root-ca"
}

最后生成证书

cfssl gencert --initca=true etcd-root-ca-csr.json | cfssljson --bare etcd-root-ca
cfssl gencert --ca etcd-root-ca.pem --ca-key etcd-root-ca-key.pem --config etcd-gencert.json etcd-csr.json | cfssljson --bare etcd

证书生成后截图如下

未分类

2.2 搭建集群

首先分发证书及 rpm 包

# 分发 rpm
for IP in `seq 5 7`; do
    scp etcd-3.2.7-1.fc28.x86_64.rpm [email protected].$IP:~
    ssh [email protected].$IP rpm -ivh etcd-3.2.7-1.fc28.x86_64.rpm
done

# 分发证书
for IP in `seq 5 7`;do
    ssh [email protected].$IP mkdir /etc/etcd/ssl
    scp *.pem [email protected].$IP:/etc/etcd/ssl
    ssh [email protected].$IP chown -R etcd:etcd /etc/etcd/ssl
    ssh [email protected].$IP chmod -R 644 /etc/etcd/ssl/*
    ssh [email protected].$IP chmod 755 /etc/etcd/ssl
done

然后修改配置如下(其他两个节点类似,只需要改监听地址和 Etcd Name 即可)

docker1.node ➜  ~ cat /etc/etcd/etcd.conf

# [member]
ETCD_NAME=etcd1
ETCD_DATA_DIR="/var/lib/etcd/etcd1.etcd"
ETCD_WAL_DIR="/var/lib/etcd/wal"
ETCD_SNAPSHOT_COUNT="100"
ETCD_HEARTBEAT_INTERVAL="100"
ETCD_ELECTION_TIMEOUT="1000"
ETCD_LISTEN_PEER_URLS="https://10.10.1.5:2380"
ETCD_LISTEN_CLIENT_URLS="https://10.10.1.5:2379,http://127.0.0.1:2379"
ETCD_MAX_SNAPSHOTS="5"
ETCD_MAX_WALS="5"
#ETCD_CORS=""

# [cluster]
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://10.10.1.5:2380"
# if you use different ETCD_NAME (e.g. test), set ETCD_INITIAL_CLUSTER value for this name, i.e. "test=http://..."
ETCD_INITIAL_CLUSTER="etcd1=https://10.10.1.5:2380,etcd2=https://10.10.1.6:2380,etcd3=https://10.10.1.7:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_ADVERTISE_CLIENT_URLS="https://10.10.1.5:2379"
#ETCD_DISCOVERY=""
#ETCD_DISCOVERY_SRV=""
#ETCD_DISCOVERY_FALLBACK="proxy"
#ETCD_DISCOVERY_PROXY=""
#ETCD_STRICT_RECONFIG_CHECK="false"
#ETCD_AUTO_COMPACTION_RETENTION="0"

# [proxy]
#ETCD_PROXY="off"
#ETCD_PROXY_FAILURE_WAIT="5000"
#ETCD_PROXY_REFRESH_INTERVAL="30000"
#ETCD_PROXY_DIAL_TIMEOUT="1000"
#ETCD_PROXY_WRITE_TIMEOUT="5000"
#ETCD_PROXY_READ_TIMEOUT="0"

# [security]
ETCD_CERT_FILE="/etc/etcd/ssl/etcd.pem"
ETCD_KEY_FILE="/etc/etcd/ssl/etcd-key.pem"
ETCD_CLIENT_CERT_AUTH="true"
ETCD_TRUSTED_CA_FILE="/etc/etcd/ssl/etcd-root-ca.pem"
ETCD_AUTO_TLS="true"
ETCD_PEER_CERT_FILE="/etc/etcd/ssl/etcd.pem"
ETCD_PEER_KEY_FILE="/etc/etcd/ssl/etcd-key.pem"
ETCD_PEER_CLIENT_CERT_AUTH="true"
ETCD_PEER_TRUSTED_CA_FILE="/etc/etcd/ssl/etcd-root-ca.pem"
ETCD_PEER_AUTO_TLS="true"

# [logging]
#ETCD_DEBUG="false"
# examples for -log-package-levels etcdserver=WARNING,security=DEBUG
#ETCD_LOG_PACKAGE_LEVELS=""

最后启动集群并测试如下

systemctl daemon-reload
systemctl start etcd
systemctl enable etcd

export ETCDCTL_API=3
etcdctl --cacert=/etc/etcd/ssl/etcd-root-ca.pem --cert=/etc/etcd/ssl/etcd.pem --key=/etc/etcd/ssl/etcd-key.pem --endpoints=https://10.10.1.5:2379,https://10.10.1.6:2379,https://10.10.1.7:2379 endpoint health

未分类

三、搭建 Master 节点

3.1 生成 Kubernetes 证书

生成证书配置文件需要借助 kubectl,所以先要安装一下 kubernetes-client 包

rpm -ivh kubernetes-client-1.8.0-1.el7.centos.x86_64.rpm

生成证书配置如下

admin-csr.json

{
  "CN": "admin",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "BeiJing",
      "L": "BeiJing",
      "O": "system:masters",
      "OU": "System"
    }
  ]
}

k8s-gencert.json

{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "kubernetes": {
        "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ],
        "expiry": "87600h"
      }
    }
  }
}

k8s-root-ca-csr.json

{
  "CN": "kubernetes",
  "key": {
    "algo": "rsa",
    "size": 4096
  },
  "names": [
    {
      "C": "CN",
      "ST": "BeiJing",
      "L": "BeiJing",
      "O": "k8s",
      "OU": "System"
    }
  ]
}

kube-proxy-csr.json

{
  "CN": "system:kube-proxy",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "BeiJing",
      "L": "BeiJing",
      "O": "k8s",
      "OU": "System"
    }
  ]
}

kubernetes-csr.json

{
    "CN": "kubernetes",
    "hosts": [
        "127.0.0.1",
        "10.254.0.1",
        "10.10.1.5",
        "10.10.1.6",
        "10.10.1.7",
        "10.10.1.8",
        "10.10.1.9",
        "localhost",
        "kubernetes",
        "kubernetes.default",
        "kubernetes.default.svc",
        "kubernetes.default.svc.cluster",
        "kubernetes.default.svc.cluster.local"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "ST": "BeiJing",
            "L": "BeiJing",
            "O": "k8s",
            "OU": "System"
        }
    ]
}

最后生成证书及配置文件

# 生成证书
cfssl gencert --initca=true k8s-root-ca-csr.json | cfssljson --bare k8s-root-ca

for targetName in kubernetes admin kube-proxy; do
    cfssl gencert --ca k8s-root-ca.pem --ca-key k8s-root-ca-key.pem --config k8s-gencert.json --profile kubernetes $targetName-csr.json | cfssljson --bare $targetName
done

# 生成配置
export KUBE_APISERVER="https://127.0.0.1:6443"
export BOOTSTRAP_TOKEN=$(head -c 16 /dev/urandom | od -An -t x | tr -d ' ')
echo "Tokne: ${BOOTSTRAP_TOKEN}"

cat > token.csv <<EOF
${BOOTSTRAP_TOKEN},kubelet-bootstrap,10001,"system:kubelet-bootstrap"
EOF

echo "Create kubelet bootstrapping kubeconfig..."
kubectl config set-cluster kubernetes 
  --certificate-authority=k8s-root-ca.pem 
  --embed-certs=true 
  --server=${KUBE_APISERVER} 
  --kubeconfig=bootstrap.kubeconfig
kubectl config set-credentials kubelet-bootstrap 
  --token=${BOOTSTRAP_TOKEN} 
  --kubeconfig=bootstrap.kubeconfig
kubectl config set-context default 
  --cluster=kubernetes 
  --user=kubelet-bootstrap 
  --kubeconfig=bootstrap.kubeconfig
kubectl config use-context default --kubeconfig=bootstrap.kubeconfig

echo "Create kube-proxy kubeconfig..."
kubectl config set-cluster kubernetes 
  --certificate-authority=k8s-root-ca.pem 
  --embed-certs=true 
  --server=${KUBE_APISERVER} 
  --kubeconfig=kube-proxy.kubeconfig
kubectl config set-credentials kube-proxy 
  --client-certificate=kube-proxy.pem 
  --client-key=kube-proxy-key.pem 
  --embed-certs=true 
  --kubeconfig=kube-proxy.kubeconfig
kubectl config set-context default 
  --cluster=kubernetes 
  --user=kube-proxy 
  --kubeconfig=kube-proxy.kubeconfig
kubectl config use-context default --kubeconfig=kube-proxy.kubeconfig

# 生成高级审计配置
cat >> audit-policy.yaml <<EOF
# Log all requests at the Metadata level.
apiVersion: audit.k8s.io/v1beta1
kind: Policy
rules:
- level: Metadata
EOF

3.2 分发 rpm 及证书

创建好证书以后就要进行分发,同时由于 Master 也作为 Node 使用,所以以下命令中在 Master 上也安装了 kubelet、kube-proxy 组件

# 分发并安装 rpm
for IP in `seq 5 7`; do
    scp kubernetes*.rpm [email protected].$IP:~; 
    ssh [email protected].$IP yum install -y kubernetes*.rpm
done

# 分发证书
for IP in `seq 5 7`;do
    ssh [email protected].$IP mkdir /etc/kubernetes/ssl
    scp *.pem [email protected].$IP:/etc/kubernetes/ssl
    scp *.kubeconfig token.csv audit-policy.yaml [email protected].$IP:/etc/kubernetes
    ssh [email protected].$IP chown -R kube:kube /etc/kubernetes/ssl
done

# 设置 log 目录权限
for IP in `seq 5 7`;do
    ssh [email protected].$IP mkdir -p /var/log/kube-audit /usr/libexec/kubernetes
    ssh [email protected].$IP chown -R kube:kube /var/log/kube-audit /usr/libexec/kubernetes
    ssh [email protected].$IP chmod -R 755 /var/log/kube-audit /usr/libexec/kubernetes
done

3.3 搭建 Master 节点

证书与 rpm 都安装完成后,只需要修改配置(配置位于 /etc/kubernetes 目录)后启动相关组件即可

  • config 通用配置
###
# kubernetes system config
#
# The following values are used to configure various aspects of all
# kubernetes services, including
#
#   kube-apiserver.service
#   kube-controller-manager.service
#   kube-scheduler.service
#   kubelet.service
#   kube-proxy.service
# logging to stderr means we get it in the systemd journal
KUBE_LOGTOSTDERR="--logtostderr=true"

# journal message level, 0 is debug
KUBE_LOG_LEVEL="--v=2"

# Should this cluster be allowed to run privileged docker containers
KUBE_ALLOW_PRIV="--allow-privileged=true"

# How the controller-manager, scheduler, and proxy find the apiserver
KUBE_MASTER="--master=http://127.0.0.1:8080"

apiserver 配置

###
# kubernetes system config
#
# The following values are used to configure the kube-apiserver
#

# The address on the local server to listen to.
KUBE_API_ADDRESS="--advertise-address=10.10.1.5 --insecure-bind-address=127.0.0.1 --bind-address=10.10.1.5"

# The port on the local server to listen on.
KUBE_API_PORT="--insecure-port=8080 --secure-port=6443"

# Port minions listen on
# KUBELET_PORT="--kubelet-port=10250"

# Comma separated list of nodes in the etcd cluster
KUBE_ETCD_SERVERS="--etcd-servers=https://10.10.1.5:2379,https://10.10.1.6:2379,https://10.10.1.7:2379"

# Address range to use for services
KUBE_SERVICE_ADDRESSES="--service-cluster-ip-range=10.254.0.0/16"

# default admission control policies
KUBE_ADMISSION_CONTROL="--admission-control=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota,NodeRestriction"

# Add your own!
KUBE_API_ARGS="--authorization-mode=RBAC,Node 
               --anonymous-auth=false 
               --kubelet-https=true 
               --enable-bootstrap-token-auth 
               --token-auth-file=/etc/kubernetes/token.csv 
               --service-node-port-range=30000-50000 
               --tls-cert-file=/etc/kubernetes/ssl/kubernetes.pem 
               --tls-private-key-file=/etc/kubernetes/ssl/kubernetes-key.pem 
               --client-ca-file=/etc/kubernetes/ssl/k8s-root-ca.pem 
               --service-account-key-file=/etc/kubernetes/ssl/k8s-root-ca.pem 
               --etcd-quorum-read=true 
               --storage-backend=etcd3 
               --etcd-cafile=/etc/etcd/ssl/etcd-root-ca.pem 
               --etcd-certfile=/etc/etcd/ssl/etcd.pem 
               --etcd-keyfile=/etc/etcd/ssl/etcd-key.pem 
               --enable-swagger-ui=true 
               --apiserver-count=3 
               --audit-policy-file=/etc/kubernetes/audit-policy.yaml 
               --audit-log-maxage=30 
               --audit-log-maxbackup=3 
               --audit-log-maxsize=100 
               --audit-log-path=/var/log/kube-audit/audit.log 
               --event-ttl=1h"

注意:API SERVER 对比 1.7 配置出现几项变动:

  • 移除了 –runtime-config=rbac.authorization.k8s.io/v1beta1 配置,因为 RBAC 已经稳定,被纳入了 v1 api,不再需要指定开启

  • –authorization-mode 授权模型增加了 Node 参数,因为 1.8 后默认 system:node role 不会自动授予 system:nodes 组,具体请参看 CHANGELOG(before-upgrading 段最后一条说明)

  • 由于以上原因,–admission-control 同时增加了 NodeRestriction 参数,关于关于节点授权器请参考 Using Node Authorization

  • 增加 –audit-policy-file 参数用于指定高级审计配置,具体可参考 CHANGELOG(before-upgrading 第四条)、Advanced audit

  • 移除 –experimental-bootstrap-token-auth 参数,更换为 –enable-bootstrap-token-auth,详情参考 CHANGELOG(Auth 第二条)

controller-manager 配置

###
# The following values are used to configure the kubernetes controller-manager

# defaults from config and apiserver should be adequate

# Add your own!
KUBE_CONTROLLER_MANAGER_ARGS="--address=0.0.0.0 
                              --service-cluster-ip-range=10.254.0.0/16 
                              --cluster-name=kubernetes 
                              --cluster-signing-cert-file=/etc/kubernetes/ssl/k8s-root-ca.pem 
                              --cluster-signing-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem 
                              --service-account-private-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem 
                              --root-ca-file=/etc/kubernetes/ssl/k8s-root-ca.pem 
                              --leader-elect=true 
                              --node-monitor-grace-period=40s 
                              --node-monitor-period=5s 
                              --pod-eviction-timeout=5m0s"

scheduler 配置

###
# kubernetes scheduler config

# default config should be adequate

# Add your own!
KUBE_SCHEDULER_ARGS="--leader-elect=true --address=0.0.0.0"

最后启动 Master 相关组件并验证

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

未分类

四、搭建 Node 节点

4.1 分发 rpm 及证书

对于 Node 节点,只需要安装 kubernetes-node 即可,同时为了方便使用,这里也安装了 kubernetes-client,如下

for IP in `seq 8 9`;do
    scp kubernetes-node-1.8.0-1.el7.centos.x86_64.rpm kubernetes-client-1.8.0-1.el7.centos.x86_64.rpm [email protected].$IP:~
    ssh [email protected].$IP yum install -y kubernetes-node-1.8.0-1.el7.centos.x86_64.rpm kubernetes-client-1.8.0-1.el7.centos.x86_64.rpm
done

同时还要分发相关证书;这里将 Etcd 证书已进行了分发,是因为 虽然 Node 节点上没有 Etcd,但是如果部署网络组件,如 calico、flannel 等时,网络组件需要联通 Etcd 就会用到 Etcd 的相关证书。

# 分发 Kubernetes 证书
for IP in `seq 8 9`;do
    ssh [email protected].$IP mkdir /etc/kubernetes/ssl
    scp *.pem [email protected].$IP:/etc/kubernetes/ssl
    scp *.kubeconfig token.csv audit-policy.yaml [email protected].$IP:/etc/kubernetes
    ssh [email protected].$IP chown -R kube:kube /etc/kubernetes/ssl
done

# 分发 Etcd 证书
for IP in `seq 8 9`;do
    ssh [email protected].$IP mkdir -p /etc/etcd/ssl
    scp *.pem [email protected].$IP:/etc/etcd/ssl
    ssh [email protected].$IP chmod -R 644 /etc/etcd/ssl/*
    ssh [email protected].$IP chmod 755 /etc/etcd/ssl
done

4.2 修改 Node 配置

Node 上只需要修改 kubelet 和 kube-proxy 的配置即可

config 通用配置

###
# kubernetes system config
#
# The following values are used to configure various aspects of all
# kubernetes services, including
#
#   kube-apiserver.service
#   kube-controller-manager.service
#   kube-scheduler.service
#   kubelet.service
#   kube-proxy.service
# logging to stderr means we get it in the systemd journal
KUBE_LOGTOSTDERR="--logtostderr=true"

# journal message level, 0 is debug
KUBE_LOG_LEVEL="--v=2"

# Should this cluster be allowed to run privileged docker containers
KUBE_ALLOW_PRIV="--allow-privileged=true"

# How the controller-manager, scheduler, and proxy find the apiserver
# KUBE_MASTER="--master=http://127.0.0.1:8080"

kubelet 配置

###
# kubernetes kubelet (minion) config

# The address for the info server to serve on (set to 0.0.0.0 or "" for all interfaces)
KUBELET_ADDRESS="--address=10.10.1.8"

# The port for the info server to serve on
# KUBELET_PORT="--port=10250"

# You may leave this blank to use the actual hostname
KUBELET_HOSTNAME="--hostname-override=docker4.node"

# location of the api-server
# KUBELET_API_SERVER=""

# Add your own!
KUBELET_ARGS="--cgroup-driver=cgroupfs 
              --cluster-dns=10.254.0.2 
              --resolv-conf=/etc/resolv.conf 
              --experimental-bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig 
              --kubeconfig=/etc/kubernetes/kubelet.kubeconfig 
              --fail-swap-on=false 
              --cert-dir=/etc/kubernetes/ssl 
              --cluster-domain=cluster.local. 
              --hairpin-mode=promiscuous-bridge 
              --serialize-image-pulls=false 
              --pod-infra-container-image=gcr.io/google_containers/pause-amd64:3.0"

注意: kubelet 配置与 1.7 版本有一定改动

  • 增加 –fail-swap-on=false 选项,否则可能导致在开启 swap 分区的机器上无法启动 kubelet,详细可参考 CHANGELOG(before-upgrading 第一条)

  • 移除 –require-kubeconfig 选项,已经过时废弃

proxy 配置

###
# kubernetes proxy config
# default config should be adequate
# Add your own!
KUBE_PROXY_ARGS="--bind-address=10.10.1.8 
                 --hostname-override=docker4.node 
                 --kubeconfig=/etc/kubernetes/kube-proxy.kubeconfig 
                 --cluster-cidr=10.254.0.0/16"

kube-proxy 配置与 1.7 并无改变,最新 1.8 的 ipvs 模式将单独写一篇文章,这里不做介绍

4.3 创建 Nginx 代理

由于 HA 方案基于 Nginx 反代实现,所以每个 Node 要启动一个 Nginx 负载均衡 Master,具体参考 HA Master 简述

nginx.conf

# 创建配置目录
mkdir -p /etc/nginx

# 写入代理配置
cat << EOF >> /etc/nginx/nginx.conf
error_log stderr notice;

worker_processes auto;
events {
  multi_accept on;
  use epoll;
  worker_connections 1024;
}

stream {
    upstream kube_apiserver {
        least_conn;
        server 10.10.1.5:6443;
        server 10.10.1.6:6443;
        server 10.10.1.7:6443;
    }

    server {
        listen        0.0.0.0:6443;
        proxy_pass    kube_apiserver;
        proxy_timeout 10m;
        proxy_connect_timeout 1s;
    }
}
EOF

# 更新权限
chmod +r /etc/nginx/nginx.conf

nginx-proxy.service

cat << EOF >> /etc/systemd/system/nginx-proxy.service
[Unit]
Description=kubernetes apiserver docker wrapper
Wants=docker.socket
After=docker.service

[Service]
User=root
PermissionsStartOnly=true
ExecStart=/usr/bin/docker run -p 127.0.0.1:6443:6443 \
                              -v /etc/nginx:/etc/nginx \
                              --name nginx-proxy \
                              --net=host \
                              --restart=on-failure:5 \
                              --memory=512M \
                              nginx:1.13.5-alpine
ExecStartPre=-/usr/bin/docker rm -f nginx-proxy
ExecStop=/usr/bin/docker stop nginx-proxy
Restart=always
RestartSec=15s
TimeoutStartSec=30s

[Install]
WantedBy=multi-user.target
EOF

最后启动 Nginx 代理即可

systemctl daemon-reload
systemctl start nginx-proxy
systemctl enable nginx-proxy

4.4 添加 Node

一切准备就绪后就可以添加 Node 了,首先由于我们采用了 TLS Bootstrapping,所以需要先创建一个 ClusterRoleBinding

# 在任意 master 执行即可
kubectl create clusterrolebinding kubelet-bootstrap 
  --clusterrole=system:node-bootstrapper 
  --user=kubelet-bootstrap

然后启动 kubelet

systemctl daemon-reload
systemctl start kubelet
systemctl enable kubelet

由于采用了 TLS Bootstrapping,所以 kubelet 启动后不会立即加入集群,而是进行证书申请,从日志中可以看到如下输出

10月 06 19:53:23 docker4.node kubelet[3797]: I1006 19:53:23.917261    3797 bootstrap.go:57] Using bootstrap kubeconfig to generate TLS client cert, key and kubeconfig file

此时只需要在 master 允许其证书申请即可

kubectl get csr | grep Pending | awk '{print $1}' | xargs kubectl certificate approve

此时可以看到 Node 已经加入了

docker1.node ➜  ~ kubectl get node
NAME           STATUS    ROLES     AGE       VERSION
docker4.node   Ready     <none>    14m       v1.8.0
docker5.node   Ready     <none>    3m        v1.8.0

最后再启动 kube-proxy 即可

systemctl start kube-proxy
systemctl enable kube-proxy

再次提醒: 如果 kubelet 启动出现了类似 system:node:xxxx 用户没有权限访问 API 的 RBAC 错误,那么一定是 API Server 授权控制器、准入控制配置有问题,请仔细阅读上面的文档进行更改

4.5 Master 作为 Node

如果想讲 Master 也作为 Node 的话,请在 Master 上安装 kubernete-node rpm 包,配置与上面基本一致;区别于 Master 上不需要启动 nginx 做负载均衡,同时 bootstrap.kubeconfig、bootstrap.kubeconfig 中的 API Server 地址改成当前 Master IP 即可。

最终成功后如下图所示

未分类

五、部署 Calico

5.1 修改 Calico 配置

Calico 部署仍然采用 “混搭” 方式,即 Systemd 控制 calico node,cni 等由 kubernetes daemonset 安装,具体请参考 Calico 部署踩坑记录(https://mritd.me/2017/07/31/calico-yml-bug/),以下直接上代码

# 获取 calico.yaml
wget https://docs.projectcalico.org/v2.6/getting-started/kubernetes/installation/hosted/calico.yaml

# 替换 Etcd 地址
sed -i 's@.*etcd_endpoints:.*@  etcd_endpoints: "https://10.10.1.5:2379,https://10.10.1.6:2379,https://10.10.1.7:2379"@gi' calico.yaml

# 替换 Etcd 证书
export ETCD_CERT=`cat /etc/etcd/ssl/etcd.pem | base64 | tr -d 'n'`
export ETCD_KEY=`cat /etc/etcd/ssl/etcd-key.pem | base64 | tr -d 'n'`
export ETCD_CA=`cat /etc/etcd/ssl/etcd-root-ca.pem | base64 | tr -d 'n'`

sed -i "s@.*etcd-cert:.*@  etcd-cert: ${ETCD_CERT}@gi" calico.yaml
sed -i "s@.*etcd-key:.*@  etcd-key: ${ETCD_KEY}@gi" calico.yaml
sed -i "s@.*etcd-ca:.*@  etcd-ca: ${ETCD_CA}@gi" calico.yaml

sed -i 's@.*etcd_ca:.*@  etcd_ca: "/calico-secrets/etcd-ca"@gi' calico.yaml
sed -i 's@.*etcd_cert:.*@  etcd_cert: "/calico-secrets/etcd-cert"@gi' calico.yaml
sed -i 's@.*etcd_key:.*@  etcd_key: "/calico-secrets/etcd-key"@gi' calico.yaml

# 注释掉 calico-node 部分(由 Systemd 接管)
sed -i '103,189s@.*@#&@gi' calico.yaml

5.2 创建 Systemd 文件

上一步注释了 calico.yaml 中 Calico Node 相关内容,为了防止自动获取 IP 出现问题,将其移动到 Systemd,Systemd service 配置如下,每个节点都要安装 calico-node 的 Service,其他节点请自行修改 ip(被问我为啥是两个反引号 \,自己试就知道了)

cat > /usr/lib/systemd/system/calico-node.service <<EOF
[Unit]
Description=calico node
After=docker.service
Requires=docker.service

[Service]
User=root
PermissionsStartOnly=true
ExecStart=/usr/bin/docker run   --net=host --privileged --name=calico-node \
                                -e ETCD_ENDPOINTS=https://10.10.1.5:2379,https://10.10.1.6:2379,https://10.10.1.7:2379 \
                                -e ETCD_CA_CERT_FILE=/etc/etcd/ssl/etcd-root-ca.pem \
                                -e ETCD_CERT_FILE=/etc/etcd/ssl/etcd.pem \
                                -e ETCD_KEY_FILE=/etc/etcd/ssl/etcd-key.pem \
                                -e NODENAME=docker1.node \
                                -e IP=10.10.1.5 \
                                -e IP6= \
                                -e AS= \
                                -e CALICO_IPV4POOL_CIDR=10.20.0.0/16 \
                                -e CALICO_IPV4POOL_IPIP=always \
                                -e CALICO_LIBNETWORK_ENABLED=true \
                                -e CALICO_NETWORKING_BACKEND=bird \
                                -e CALICO_DISABLE_FILE_LOGGING=true \
                                -e FELIX_IPV6SUPPORT=false \
                                -e FELIX_DEFAULTENDPOINTTOHOSTACTION=ACCEPT \
                                -e FELIX_LOGSEVERITYSCREEN=info \
                                -v /etc/etcd/ssl/etcd-root-ca.pem:/etc/etcd/ssl/etcd-root-ca.pem \
                                -v /etc/etcd/ssl/etcd.pem:/etc/etcd/ssl/etcd.pem \
                                -v /etc/etcd/ssl/etcd-key.pem:/etc/etcd/ssl/etcd-key.pem \
                                -v /var/run/calico:/var/run/calico \
                                -v /lib/modules:/lib/modules \
                                -v /run/docker/plugins:/run/docker/plugins \
                                -v /var/run/docker.sock:/var/run/docker.sock \
                                -v /var/log/calico:/var/log/calico \
                                quay.io/calico/node:v2.6.1
ExecStop=/usr/bin/docker rm -f calico-node
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

5.3 修改 kubelet 配置

根据官方文档要求 kubelet 配置必须增加 –network-plugin=cni 选项,所以需要修改 kubelet 配置

###
# kubernetes kubelet (minion) config
# The address for the info server to serve on (set to 0.0.0.0 or "" for all interfaces)
KUBELET_ADDRESS="--address=10.10.1.5"
# The port for the info server to serve on
# KUBELET_PORT="--port=10250"
# You may leave this blank to use the actual hostname
KUBELET_HOSTNAME="--hostname-override=docker1.node"
# location of the api-server
# KUBELET_API_SERVER=""
# Add your own!
KUBELET_ARGS="--cgroup-driver=cgroupfs 
              --network-plugin=cni 
              --cluster-dns=10.254.0.2 
              --resolv-conf=/etc/resolv.conf 
              --experimental-bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig 
              --kubeconfig=/etc/kubernetes/kubelet.kubeconfig 
              --fail-swap-on=false 
              --cert-dir=/etc/kubernetes/ssl 
              --cluster-domain=cluster.local. 
              --hairpin-mode=promiscuous-bridge 
              --serialize-image-pulls=false 
              --pod-infra-container-image=gcr.io/google_containers/pause-amd64:3.0"

然后重启即可

systemctl daemon-reload
systemctl restart kubelet

此时执行 kubectl get node 会看到 Node 为 NotReady 状态,属于正常情况

5.4 创建 Calico Daemonset

# 先创建 RBAC
kubectl apply -f https://docs.projectcalico.org/v2.6/getting-started/kubernetes/installation/rbac.yaml

# 再创建 Calico Daemonset
kubectl create -f calico.yaml

5.5 创建 Calico Node

Calico Node 采用 Systemd 方式启动,在每个节点配置好 Systemd service后,每个节点修改对应的 calico-node.service 中的 IP 和节点名称,然后启动即可

systemctl daemon-reload
systemctl restart calico-node
sleep 5
systemctl restart kubelet

此时检查 Node 应该都处于 Ready 状态

未分类

最后测试一下跨主机通讯

# 创建 deployment
cat << EOF >> demo.deploy.yml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: demo-deployment
spec:
  replicas: 5
  selector:
    matchLabels:
      app: demo
  template:
    metadata:
      labels:
        app: demo
    spec:
      containers:
      - name: demo
        image: mritd/demo
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
EOF
kubectl create -f demo.deploy.yml

进入其中一个 Pod,ping 另一个 Pod 的 IP 测试即可

未分类

六、部署 DNS

6.1 部署集群 DNS

DNS 组件部署非常简单,直接创建相应的 deployment 等即可;但是有一个事得说一嘴,Kubernets 一直在推那个 Addon Manager 的工具来管理 DNS 啥的,文档说的条条是道,就是不希望我们手动搞这些东西,防止意外修改云云… 但问题是关于那个 Addon Manager 咋用一句没提,虽然说里面就一个小脚本,看看也能懂;但是我还是选择手动 ????… 还有这个 DNS 配置文件好像又挪地方了,以前在 contrib 项目下的…

# 获取文件
wget https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/kube-dns.yaml.sed
mv kube-dns.yaml.sed kube-dns.yaml

# 修改配置
sed -i 's/$DNS_DOMAIN/cluster.local/gi' kube-dns.yaml
sed -i 's/$DNS_SERVER_IP/10.254.0.2/gi' kube-dns.yaml

# 创建
kubectl create -f kube-dns.yaml

创建好以后如下所示

未分类

然后创建两组 Pod 和 Service,进入 Pod 中 curl 另一个 Service 名称看看是否能解析;同时还要测试一下外网能否解析

未分类

测试外网

未分类

6.2 部署 DNS 自动扩容部署

这个同样下载 yaml,然后创建一下即可,不需要修改任何配置

wget https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns-horizontal-autoscaler/dns-horizontal-autoscaler.yaml
kubectl create -f dns-horizontal-autoscaler.yaml

部署完成后如下

未分类

自动扩容这里不做测试了,虚拟机吃不消了,详情自己参考 https://kubernetes.io/docs/tasks/administer-cluster/dns-horizontal-autoscaling/

Kubernetes之“暂停”容器

【编者的话】希望这篇文章可以帮助大家更好的了解Kubernetes的相关核心内容。

当检查你的Kubernetes集群的节点时,在节点上执行命令docker ps,你可能会注意到一些被称为“暂停(/pause)”的容器。

$ docker ps
CONTAINER ID IMAGE COMMAND ...
...
3b45e983c859 gcr.io/google_containers/pause-amd64:3.0  “/pause” ...
...
dbfc35b00062 gcr.io/google_containers/pause-amd64:3.0  “/pause” ...
...
c4e998ec4d5d gcr.io/google_containers/pause-amd64:3.0  “/pause” ...
...
508102acf1e7 gcr.io/google_containers/pause-amd64:3.0  “/pause” ...

这些“暂停”容器是啥,而且还这么多暂停的?这到底是什么情况?

未分类

为了回答这些问题,我们需要退一步看看Kubernetes中的pods如何实现,特别是在Docker/containerd运行时。如果你还不知道怎么做,可以先阅读我以前发表的关于Kubernetes pods 的文章(http://dockone.io/article/2682)。

Docker支持容器,这非常适合部署单个软件单元。但是,当你想要一起运行多个软件时,这种模式会变得有点麻烦。当开发人员创建使用supervisord作为入口点的Docker镜像来启动和管理多个进程时,你经常会看到这一点。对于生产系统,许多人发现,将这些应用程序部署在部分隔离并部分共享环境的容器组中更为有用。

Kubernetes为这种使用场景提供了一个称为Pod的干净的抽象。它在隐藏Docker标志的复杂性的同时会保留容器,共享卷等。它也隐藏了容器运行时的差异。例如,rkt原生支持Pod,所以Kubernetes的工作要少一些,但是不管怎样,作为Kubernetes用户的你都不用担心(Pod会帮你搞定这些)。

原则上,任何人都可以配置Docker来控制容器组之间的共享级别——你只需创建一个父容器,在知道正确的标志配置的情况下来设置并创建共享相同环境的新容器,然后管理这些容器的生命周期。而管理所有这些片段的生命周期可能会变得相当复杂。

在Kubernetes中,“暂停”容器用作你的Pod中所有容器的“父容器”。“暂停”容器有两个核心职责。首先,在Pod中它作为Linux命名空间共享的基础。其次,启用PID(进程ID)命名空间共享,它为每个Pod提供PID 1,并收集僵尸进程。

共享命名空间

在Linux中,当你运行新进程时,该进程从父进程继承其命名空间。在新命名空间中运行进程的方法是通过“取消共享”命名空间(与父进程),从而创建一个新的命名空间。以下是使用该unshare工具在新的PID,UTS,IPC和装载命名空间中运行shell的示例。

sudo unshare --pid --uts --ipc --mount -f chroot rootfs / bin / sh

一旦进程运行,你可以将其他进程添加到进程的命名空间中以形成一个Pod。可以使用setns系统调用将新进程添加到现有命名空间。

Pod中的容器在其中共享命名空间。Docker可让你自动执行此过程,因此,让我们来看一下如何使用“暂停”容器和共享命名空间从头开始创建Pod的示例。首先,我们将需要使用Docker启动“暂停”容器,以便我们可以将容器添加到Pod中。

docker run -d --name pause gcr.io/google_containers/pause-amd64:3.0

然后我们可以运行我们的Pod的容器。首先我们将运行Nginx。这将在端口2368上设置Nginx到其localhost的代理请求。

$ cat <<EOF >> nginx.conf
> error_log stderr;
> events { worker_connections  1024; }
> http {
>     access_log /dev/stdout combined;
>     server {
>         listen 80 default_server;
>         server_name example.com www.example.com;
>         location / {
>             proxy_pass http://127.0.0.1:2368;
>         }
>     }
> }
> EOF
$ docker run -d --name nginx -v `pwd`/nginx.conf:/etc/nginx/nginx.conf -p 8080:80 --net=container:pause --ipc=container:pause --pid=container:pause nginx 

然后,我们将为作为我们的应用服务器的ghost博客应用程序创建另一个容器。

$ docker run -d --name ghost --net = container:pause --ipc = container:pause --pid = container:pause ghost

在这两种情况下,我们将“暂停”容器指定为我们要加入其命名空间的容器。这将有效地创建我们的Pod。如果你访问 http://localhost:8080/ 你应该能够看到ghost通过Nginx代理运行,因为网络命名空间在pause,nginx和ghost容器之间共享。

未分类

如果你觉得这一切好复杂,恭喜你,大家都这么就觉得;它确实很复杂(感觉像句废话)。而且我们甚至还没有了解如何监控和管理这些容器的生命周期。不过,值得庆幸的事,通过Pod,Kubernetes会为你很好地管理所有这些。

收割僵尸

在Linux中,PID命名空间中的所有进程会形成一个树结构,每个进程都会有一个父进程。只有在树的根部的进程没有父进程。这个进程就是“init”进程,即PID 1。

进程可以使用fork和exec syscalls启动其他进程。当启动了其他进程,新进程的父进程就是调用fork syscall的进程。fork用于启动正在运行的进程的另一个副本,而exec则用于启动不同的进程。每个进程在OS进程表中都有一个条目。这将记录有关进程的状态和退出代码。当子进程运行完成,它的进程表条目仍然将保留直到父进程使用wait syscall检索其退出代码将其退出。这被称为“收割”僵尸进程。

未分类

僵尸进程是已停止运行但进程表条目仍然存在的进程,因为父进程尚未通过wait syscall进行检索。从技术层面来说,终止的每个进程都算是一个僵尸进程,尽管只是在很短的时间内发生的,但只要不终止他们就可以存活更久。

当父进程wait在子进程完成后不调用syscall时,会发生较长的生存僵尸进程。这样的情况有很多,比如:当父进程写得不好并且简单地省略wait call时,或者当父进程在子进程之前死机,并且新的父进程没有调用wait去检索子进程时。当进程的父进程在子进程之前死机时,OS将子进程分配给“init”进程即PID 1。init进程“收养”子进程并成为其父进程。这意味着现在当子进程退出新的父进程(init)时,必须调用wait 来获取其退出代码否则其进程表项将保持永远,并且它也将成为一个僵尸进程。

在容器中,一个进程必须是每个PID命名空间的init进程。使用Docker,每个容器通常都有自己的PID命名空间,ENTRYPOINT进程是init进程。然而,正如我在上一篇关于Kubernetes Pods的文章 (http://dockone.io/article/2682) 中所指出的,某个容器可以在另一个容器的命名空间中运行。在这种情况下,这个容器必须承担init进程的角色,而其他容器则作为init进程的子进程添加到命名空间中。

在Kubernetes Pods的文章中,我在一个容器中运行Nginx,并将ghost添加到了Nginx容器的PID命名空间。

$ docker run -d --name nginx -v `pwd`/nginx.conf:/etc/nginx/nginx.conf -p 8080:80 nginx
$ docker run -d --name ghost --net=container:nginx --ipc=container:nginx --pid=container:nginx ghost

在这种情况下,Nginx将承担PID 1的作用,并将ghost添加为Nginx的子进程。虽然这样貌似不错,但从技术上来看,Nginx现在需要负责任何ghost进程的子进程。例如,如果ghost分身或者使用子进程运行exec,并在子进程完成之前崩溃,那么这些子进程将被Nginx收养。但是,Nginx并不是设计用来作为一个init进程运行并收割僵尸进程的。这意味着将会有很多的这种僵尸进程,并且在整个容器的生命周期,他们都将持续存活。

在Kubernetes Pods中,容器的运行方式与上述方式大致相同,但是每个Pod都有一个特殊的“暂停”容器。这个“暂停”容器运行一个非常简单的进程,它不执行任何功能,基本上是永远睡觉的(见pause()下面的调用)。因为它比较简单,在这里写下完整的源代码,如下:

/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
include <signal.h>

include <stdio.h>

include <stdlib.h>

include <sys/types.h>

include <sys/wait.h>

include <unistd.h>

static void sigdown(int signo) {
psignal(signo, "Shutting down, got signal");
exit(0);
}

static void sigreap(int signo) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}

int main() {
if (getpid() != 1)
/* Not an error because pause sees use outside of infra containers. */
fprintf(stderr, "Warning: pause should be the first processn");

if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
return 1;
if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
return 2;
if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap,
                                         .sa_flags = SA_NOCLDSTOP},
            NULL) < 0)
return 3;

for (;;)
pause();
fprintf(stderr, "Error: infinite loop terminatedn");
return 42;
} 

正如你所看到的,它当然不会只知道睡觉。它执行另一个重要的功能——即它扮演PID 1的角色,并在子进程被父进程孤立的时候通过调用wait 来收割这些僵尸子进程(参见sigreap)。这样我们就不用担心我们的Kubernetes Pods的PID命名空间里会堆满僵尸了。

PID命名空间共享的一些上下文

值得注意的是,PID命名空间共享已经有了很多的前后关系。如果你启用了PID命名空间共享,那么只能通过暂停容器来收割僵尸,并且目前这一功能仅在Kubernetes 1.7+以上的版本中可用。如果使用Docker 1.13.1+运行Kubernetes 1.7,这一功能默认是开启的,除非使用kubelet标志(–docker-disable-shared-pid=true)禁用。这在Kubernetes 1.8 中正好相反的,现在默认情况下是禁用的,除非由kubelet标志(–docker-disable-shared-pid=false)启用。感兴趣的话,可以看看在GitHub issue中对增加支持PID命名空间共享的有关讨论。

如果没有启用PID命名空间共享,则Kubernetes Pod中的每个容器都将具有自己的PID 1,并且每个容器将需要收集僵尸进程本身。很多时候,这不是一个问题,因为应用程序不会产生其他进程,但僵尸进程使用内存是一个经常被忽视的问题。因此,由于PID命名空间共享使你能够在同一个Pod中的容器之间发送信号,我衷心的希望PID命名空间共享可以成为Kubernetes中的默认选项。

k8s实战-创建ConfigMap

k8s的ConfigMap用来保存配置数据,以键值对形式存储,既可以保存单个属性,也可以保存配置文件。使用ConfigMao前请确保已经安装好了k8s集群,在master主机上执行kubectl create configmap –help,可以看到该命令的使用方法kubectl create configmap map-name map-source。

kubectl create configmap my-config –from-file=path/to/dir

  
该命令以文件目录为源创建ConfigMap,key为文件名,value为文件内容,子文件夹及其下文件将被忽略,例如,k8s-cfg文件夹下有4个文件,文件结构及内容为:

[root@niuhp-vm tmp]# cat k8s-cfg/dir1/file4.data
i am in dir1
[root@niuhp-vm tmp]# cat k8s-cfg/file1
abcdefg
[root@niuhp-vm tmp]# cat k8s-cfg/file2.text
1234567
[root@niuhp-vm tmp]# cat k8s-cfg/file3.log
k1=adsdf,k2=23424,k3=35434

在控制台执行kubectl create configmap my-config-from-dir –from-file=k8s-cfg,成功的话我们会看到如下提示:

[root@niuhp-vm tmp]# create configmap my-config-from-dir --from-file=k8s-cfg
configmap "my-config-from-dir" created

从控制台看下这个ConfigMap的内容

[root@niuhp-vm tmp]# kubectl describe configmap my-config-from-dir
Name:           my-config-from-dir
Namespace:      default
Labels:         <none>
Annotations:    <none>
Data
====
file1:
----
abcdefg
file2.text:
----
1234567
file3.log:
----
k1=adsdf,k2=23424,k3=35434
Events: <none>

从dashboad看下

未分类

另外可以通过参数–namespace={namespace-name}指定命令空间。

kubectl create configmap my-config –from-file=[key1=]/path/to/file1.txt –from-file=[key2=]/path/to/file2.txt

  
该命令以多个文件为源创建ConfigMap,key为文件名(也可以指定),value为文件内容,例如执行create configmap my-config-from-files –from-file=k8s-cfg/file1 –from-file=k8s-cfg/dir1/file4.data创建的ConfigMap为

未分类

执行kubectl create configmap my-config-from-files-custom-key –from-file=mykey1=k8s-cfg/file1 –from-file=mykey2=k8s-cfg/dir1/file4.data创建的ConfigMap为

未分类

kubectl create configmap my-config –from-literal=key1=config1 –from-literal=key2=config2

  
该命令以输入的多个键值对为源创建ConfigMap,例如执行kubectl create configmap my-config-from-kv –from-literal=mykeyzh=nihao –from-literal=mykeyen=hello –from-literal=mykeynum=12345创建的ConfigMap为

未分类

kubectl create configmap my-config –from-env-file=path/to/file

  
该命令以存放键值对的文件为源创建ConfigMap,例如:文件 k8s-test.properties中文件内容如下

[root@niuhp-vm tmp]# cat k8s-test.properties
a=1
b=2
c=3
key2=2sfsdf

执行kubectl create configmap my-config-from-envfile –from-env-file=k8s-test.properties创建的ConfigMap为

未分类

Debian配置iptables

Debian默认已经安装iptables,查看规则iptables -L默认允许所有出入,这是非常不安全的,因此需要对规则进行调整。

编辑配置文件:

/etc/iptables.test.rules

添加下面的规则,请根据实际情况调整:

*filter

# Allows all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -d 127.0.0.0/8 -j REJECT

# Accepts all established inbound connections
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allows all outbound traffic
# You could modify this to only allow certain traffic
-A OUTPUT -j ACCEPT

# Allows HTTP and HTTPS connections from anywhere (the normal ports for websites)
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT

# Allows SSH connections 
# The --dport number is the same as in /etc/ssh/sshd_config
-A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT

# Now you should read up on iptables rules and consider whether ssh access 
# for everyone is really desired. Most likely you will only allow access from certain IPs.

# Allow ping
#  note that blocking other types of icmp packets is considered a bad idea by some
#  remove -m icmp --icmp-type 8 from this line to allow all kinds of icmp:
#  https://security.stackexchange.com/questions/22711
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT

# log iptables denied calls (access via 'dmesg' command)
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7

# Reject all other inbound - default deny unless explicitly allowed policy:
-A INPUT -j REJECT
-A FORWARD -j REJECT

COMMIT

激活规则:

iptables-restore < /etc/iptables.test.rules

保存规则到主配置文件:

iptables-save > /etc/iptables.up.rules

开机自动加载规则

#编辑配置
/etc/network/if-pre-up.d/iptables

添加如下内容

#!/bin/sh
 /sbin/iptables-restore < /etc/iptables.up.rules

添加执行权限

chmod +x /etc/network/if-pre-up.d/iptables

OK,一切皆已搞定,感觉比CentOS的iptables要麻烦一点。

如何让 Docker 容器正常打印 Python 的日志

在 Docker 容器里跑 Python 程序时,我们经常遇到通过print函数或者logging模块输出的信息在容器 log 中迷之失踪,过了好久又迷之出现。这是因为 Python 在写 stdout 和 stderr 的时候有缓冲区,导致输出无法实时更新进容器 log。

有如下几种方法解决:

1. 增加环境变量

对于使用print函数打印的内容,在运行容器时增加环境变量PYTHONUNBUFFERED=0就可以解决。

2. 配置 logging 的 stream 参数

import logging
logging.basicConfig(stream=sys.stdout)

这样,通过 logging 模块打印的日志都会直接写到标准输出 stdout。

或者自定义两个StreamHandler分别配置为输出到 stdout 和 stderr,来对不同 log 分别进行输出处理。

3. WSGI server 配置参数

如果是以 WSGI server 运行的 web 应用,以 gunicorn 为例,在 gunicorn 的启动命令中增加参数–access-logfile – –error-logfile -即可。

docker~使用阿里加速器

国外的docker hub速度慢这是公认的,而我们可以使用阿里提供的加速器,管理你的镜像,拉别人的镜像等等.

1、注册一个阿里的账号

2、进行加速器页面https://cr.console.aliyun.com/#/accelerator

3、复制你的加入器URL

4、将加速参数添加到docker启动项

echo “DOCKER_OPTS=”$DOCKER_OPTS –registry-mirror=你的加速地址”” | sudo tee -a /etc/default/docker
sudo service docker restart

5、也可以将加速器地址写到配置文件里,然后重启服务即可

sudo mkdir -p /etc/docker

sudo tee /etc/docker/daemon.json <<-'EOF'

{

  "registry-mirrors": ["https://d8b3zdiw.mirror.aliyuncs.com"]

}

EOF

sudo systemctl daemon-reload

sudo systemctl restart docker[/code]

6、如果客户端docker版本低,也可以去升级一下

curl -sSL http://acs-public-mirror.oss-cn-hangzhou.aliyuncs.com/docker-engine/internet | sh -[/code]

7、可以在阿里网页上搜索你需要的镜像,https://cr.console.aliyun.com/#/imageSearch

未分类

8、点击详情,可以看到镜像相关信息,和如何拉取你的镜像

未分类

9、建立自己的仓库之后,可以push和pull自己的镜像了

未分类

感谢各位的阅读,我们对docker的研究还在继续!

centos7下docker二进制文件编译

系统环境与软件版本

  • OS:Centos7 64bit

  • Kernel Version:3.10.0-693.2.2.el7.x86_64

  • Golang Version: go1.8.4 linux/amd64

  • Docker: 17.05.0-ce

浅谈docker源码编译

官方提供编译步骤依次为:make build和make binary。先看懂Makefile会帮助理解docker基本结构。

  • make build
    其实就是docker build,于是要看Dockerfile文件。其制作一个叫docker-dev的镜像,镜像中会生成源码编译的环境。

  • make binary
    其实就是docker run docker-dev,即运行docker-dev一个容器,并在容器中的bundles文件夹下生成dockers所需的二进制文件。

通过查看Dockerfile以下内容:

# Install tomlv, vndr, runc, containerd, tini, docker-proxy
# Please edit hack/dockerfile/install-binaries.sh to update them.
COPY hack/dockerfile/binaries-commits /tmp/binaries-commits
COPY hack/dockerfile/install-binaries.sh /tmp/install-binaries.sh
RUN /tmp/install-binaries.sh tomlv vndr runc containerd tini proxy bindata

可以看出具体的binary来自脚本install-binaries.sh。

  • install-binaries.sh
    此脚本涉及到docker-containerd系列,docker-runc,docker-init和docker-proxy等组件的源码地址,以及编译命令

  • binaries-commits
    此文件涉及到各类组件的commit编号,使用git checkout -q xxxxxxxx来切换到相对应的tree上

以下内容,实际便是抽出install-binaries.sh中的内容,独立完成,从而获得docker所有编译后的二进制文件。如要生成rpm文件,需进一步研究

Golang的安装与配置

可以在 下载 – Golang中国 (https://www.golangtc.com/download) 中下载相对应的安装包。安装包go1.9.linux-amd64.tar.gz和脚本install_go.sh放在同一个目录下。

代码下载: https://o-my-chenjian.com/download/Make-Docker-Executable-File-On-CentOS7/install_go.sh

#!/bin/bash

cur_path=`pwd`

# 解压go包
sudo tar zxvf ${cur_path}/go1.8.4.linux-amd64.tar.gz -C /usr/local

# 创建GOPATH文件夹
sudo mkdir -p /home/mygo

# 设置go环境变量
sudo echo "export GOROOT=/usr/local/go" >> /etc/profile
sudo echo "export GOPATH=/home/mygo" >> /etc/profile
sudo echo "export PATH=$PATH:$GOROOT/bin" >> /etc/profile
. /etc/profile

# 安装wget
sudo yum install -y wget

# 更新为aliyun源
sudo wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

# 安装git
sudo yum install -y git

# 安装go包管理工具govendor
go get -u github.com/kardianos/govendor
cp ${GOPATH}/bin/govendor /usr/local/go/bin/

结果如下:

go version
<<'COMMENT'
go version go1.8.4 linux/amd64
COMMENT

ls /home/mygo/ /home/mygo/src/ /home/mygo/src/github.com/ /usr/local/go/bin/

<<'COMMENT'
/home/mygo/:
bin  pkg  src

/home/mygo/src/:
github.com

/home/mygo/src/github.com/:
kardianos

/usr/local/go/bin/:
go  godoc  gofmt  govendor
COMMENT

docker编译二进制文件

安装依赖软件

yum install -y gcc make cmake device-mapper-devel btrfs-progs-devel libarchive libseccomp-devel glibc-static

编译docker等二进制文件

组件:

  • docker: docker-client端
  • dockerd: docker-server端

操作:

cd $GOPATH/src/github.com/

mkdir docker && cd docker

# 下载相关版本的docker源码
git clone -b v17.05.0-ce https://github.com/moby/moby.git

cp -R moby/ docker && rm -rf moby/

# docker编译
cd  $GOPATH/src/github.com/docker/docker/cmd/docker
go build
cp docker /usr/local/bin/

# dockerd编译
cd  $GOPATH/src/github.com/docker/docker/cmd/dockerd
go build
cp dockerd /usr/local/bin/

编译containerd等二进制文件

组件:

  • docker-containerd
  • docker-containerd-ctr
  • docker-containerd-shim

操作:

# 下载相关版本的container源码
git clone https://github.com/containerd/containerd.git "${GOPATH}/src/github.com/docker/containerd"
cd "${GOPATH}/src/github.com/docker/containerd"
git checkout -q 9048e5e50717ea4497b757314bad98ea3763c145

# 组件编译
cd  ${GOPATH}/src/github.com/docker/containerd

make static
<<'COMMENT'
cd ctr && go build -ldflags "-w -extldflags -static -X github.com/docker/containerd.GitCommit=9048e5e50717ea4497b757314bad98ea3763c145 " -tags "" -o ../bin/ctr
cd containerd && go build -ldflags "-w -extldflags -static -X github.com/docker/containerd.GitCommit=9048e5e50717ea4497b757314bad98ea3763c145 " -tags "" -o ../bin/containerd
cd containerd-shim && go build -ldflags "-w -extldflags -static -X github.com/docker/containerd.GitCommit=9048e5e50717ea4497b757314bad98ea3763c145 " -tags "" -o ../bin/containerd-shim
COMMENT

cp bin/containerd /usr/local/bin/docker-containerd
cp bin/containerd-shim /usr/local/bin/docker-containerd-shim
cp bin/ctr /usr/local/bin/docker-containerd-ctr

编译docker-runc二进制文件

组件:

  • docker-runc

操作:

cd $GOPATH/src/github.com/

mkdir opencontainers && cd opencontainers

# 下载相关版本的runc源码
git clone -b v1.0.0-rc2 https://github.com/opencontainers/runc.git "${GOPATH}/src/github.com/opencontainers/runc"

# runc编译
cd  ${GOPATH}/src/github.com/opencontainers/runc

make BUILDTAGS="${RUNC_BUILDTAGS:-"selinux"}" static

<<'COMMENT'
CGO_ENABLED=1 go build -i -tags "selinux cgo static_build" -ldflags "-w -extldflags -static -X main.gitCommit="c91b5bea4830a57eac7882d7455d59518cdf70ec-dirty" -X main.version=1.0.0-rc2" -o runc .
COMMENT

cp runc /usr/local/bin/docker-runc

编译docker-init二进制文件

组件:

docker-init

操作:

cd $GOPATH/src/github.com/

mkdir krallin && cd krallin

# 下载相关版本的tini源码
git clone https://github.com/krallin/tini.git "$GOPATH/tini"
cd "$GOPATH/tini"
git checkout -q 949e6facb77383876aeff8a6944dde66b3089574

cmake .
<<'COMMENT'
-- The C compiler identification is GNU 4.8.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Performing Test HAS_BUILTIN_FORTIFY
-- Performing Test HAS_BUILTIN_FORTIFY - Failed
-- Configuring done
-- Generating done
-- Build files have been written to: /home/mygo/tini
COMMENT

make tini-static
<<'COMMENT'
Scanning dependencies of target tini-static
[100%] Building C object CMakeFiles/tini-static.dir/src/tini.c.o
Linking C executable tini-static
[100%] Built target tini-static
COMMENT

cp tini-static /usr/local/bin/docker-init

编译docker-proxy二进制文件

组件:

docker-proxy

操作:

cd $GOPATH/src/github.com/docker

# 下载相关版本的proxy源码
git clone https://github.com/docker/libnetwork.git "$GOPATH/src/github.com/docker/libnetwork"
cd "$GOPATH/src/github.com/docker/libnetwork"
git checkout -q 7b2b1feb1de4817d522cc372af149ff48d25028

# proxy编译
go build -ldflags="$PROXY_LDFLAGS" -o /usr/local/bin/docker-proxy github.com/docker/libnetwork/cmd/proxy

运行docker

ll /usr/local/bin/docker*

<<'COMMENT'
-rwxr-xr-x. 1 root root 25845680 Oct 10 14:00 /usr/local/bin/docker
-rwxr-xr-x. 1 root root 12474568 Oct 10 14:01 /usr/local/bin/docker-containerd
-rwxr-xr-x. 1 root root 11435336 Oct 10 14:03 /usr/local/bin/docker-containerd-ctr
-rwxr-xr-x. 1 root root  3858880 Oct 10 14:04 /usr/local/bin/docker-containerd-shim
-rwxr-xr-x. 1 root root 55072232 Oct 10 14:02 /usr/local/bin/dockerd
-rwxr-xr-x. 1 root root   824568 Oct 10 14:59 /usr/local/bin/docker-init
-rwxr-xr-x. 1 root root  2528043 Oct 10 15:10 /usr/local/bin/docker-proxy
-rwxr-xr-x. 1 root root 10894408 Oct 10 14:16 /usr/local/bin/docker-runc
COMMENT

groupadd docker

dockerd
<<'COMMENT'
INFO[0000] libcontainerd: new containerd process, pid: 9024 
WARN[0000] containerd: low RLIMIT_NOFILE changing to max  current=1024 max=4096
WARN[0001] failed to rename /var/lib/docker/tmp for background deletion: %!s(<nil>). Deleting synchronously 
INFO[0001] [graphdriver] using prior storage driver: overlay 
INFO[0001] Graph migration to content-addressability took 0.00 seconds 
INFO[0001] Loading containers: start.
INFO[0001] Default bridge (docker0) is assigned with an IP address 172.17.0.0/16. Daemon option --bip can be used to set a preferred IP address 
INFO[0001] Loading containers: done.
INFO[0001] Daemon has completed initialization
INFO[0001] Docker daemon                                 commit=library-import graphdriver=overlay version=library-import
INFO[0001] API listen on /var/run/docker.sock
COMMENT

ps aux|grep docker
<<'COMMENT'
root     11753  0.2  2.8 343472 28860 pts/1    Sl+  14:09   0:00 dockerd
root     11756  0.0  0.6 267908  6240 ?        Ssl  14:09   0:00 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc
root     11890  0.0  0.0 112660   976 pts/2    R+   14:10   0:00 grep --color=auto docker
COMMENT

docker version
<<'COMMENT'
Client:
 Version:      library-import
 API version:  1.29
 Go version:   go1.8.4
 Git commit:   library-import
 Built:        library-import
 OS/Arch:      linux/amd64

Server:
 Version:      library-import
 API version:  1.29 (minimum version 1.12)
 Go version:   go1.8.4
 Git commit:   library-import
 Built:        library-import
 OS/Arch:      linux/amd64
 Experimental: false
COMMENT

docker pull busybox

docker run -idt busybox
<<'COMMENT'
b6f61b4b5ec9ce2b4331a47a1e8a3552b2162ca0f152ec2e02dfbf169d64a80a
COMMENT

docker ps -a
<<'COMMENT'
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
b6f61b4b5ec9        busybox             "sh"                27 minutes ago      Up 27 minutes                           loving_noether
COMMENT

可以看到,在启动dockerd后,会启动另一个程序docker-containerd。

制作docker的systemd-unit

docker.service

cat > docker.service <<EOF
[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.io

[Service]
Environment="PATH=/usr/local/bin:/bin:/sbin:/usr/bin:/usr/sbin"
EnvironmentFile=-/run/flannel/docker
ExecStart=/usr/local/bin/dockerd --log-level=error $DOCKER_NETWORK_OPTIONS
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
Delegate=yes
KillMode=process

[Install]
WantedBy=multi-user.target
EOF

启动docker服务

sudo cp docker.service /etc/systemd/system/docker.service
sudo systemctl daemon-reload

sudo systemctl enable docker
<<'COMMENT'
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /etc/systemd/system/docker.service.
COMMENT

sudo systemctl start docker

sudo systemctl status docker
<<'COMMENT'
● docker.service - Docker Application Container Engine
   Loaded: loaded (/etc/systemd/system/docker.service; enabled; vendor preset: disabled)
   Active: active (running) since Wed 2017-10-11 15:27:51 CST; 3s ago
     Docs: http://docs.docker.io
 Main PID: 1152 (dockerd)
   Memory: 67.2M
   CGroup: /system.slice/docker.service
           ├─1152 /usr/local/bin/dockerd --log-level=error
           └─1156 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd...

Oct 11 15:27:51 localhost.localdomain systemd[1]: Started Docker Application Container Engine.
Oct 11 15:27:51 localhost.localdomain systemd[1]: Starting Docker Application Container Engine...
COMMENT

TroubleShooting

  • /bin/bash^M: bad interpreter:没有那个文件或目录

参考: http://blog.csdn.net/yongan1006/article/details/8142527

  • Linux环境下gcc静态编译/usr/bin/ld: cannot find -lc错误

参考: http://blog.csdn.net/shudaqi2010/article/details/32938715

docker国内镜像设置

linux系统

如果存在/etc/docker/目录,那么恭喜,很简单,在该目录下添加一个叫做daemon.json的文件。

内容如下:

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

然后重新启动docker daemon服务即可。

上述方法是参考:
https://docs.docker.com/registry/recipes/mirror/#use-case-the-china-registry-mirror

mac系统

mac系统有docker设置界面。在右上角docker小鲸鱼图标的Preference里面的Advanced里面的Registry mirrors里面添加https://registry.docker-cn.com即可。然后重新启动docker。