Apache Spark on K8s的安全性和性能优化

前言

Apache Spark是目前最为流行的大数据计算框架,与Hadoop相比,它是替换MapReduce组件的不二选择,越来越多的企业正在从传统的MapReduce作业调度迁移到Spark上来,Spark的生态圈支持者越来越多,当然它出众的内部API设计,让它也非常容易和现有既成事实的Hadoop组件(YARN/HDFS)集成。

容器技术的兴起,各种分布式的容器编排技术也应运而生,其中的佼佼者包括Apache Mesos和Google发起的Kubernetes,虽然Mesos系出名门(UC Berkely),但是K8s最近1-2年逐渐有拉大领先差距的趋势。

大数据计算框架,存储框架和云计算容器技术,这两者在数据中心的技术栈中,早期都是各自独立发展,并无太大关联关系,伴随各自技术生态圈日益成熟,几个大的技术框架各自领地划分也非常清楚,大数据计算服务与其他云计算服务并无二致,需要统一的资源调度与协调框架,让它的部署更加方便,资源利用率更加高效;从Mesos或者K8S的角度,作为致力于成为数据中心的OS(操作系统)的资源调度框架,放弃掉数据中心最大的应用场景之一的大数据计算服务,貌似也说不过去,于是,这两者慢慢也走在了一起。这篇文章,我们来探讨它们的重要代表,Apache Spark和K8s,当它们走在一起的时候,会碰到什么样的问题,以及社区的最新进展。

Apache Spark的分布式调度框架

回顾历史,Spark的资源调度器有这么几个实现:

  • Local模式,主要用于Spark应用程序开发调试使用,毕竟是单机单进程,设置断点,单步跟踪都十分方便;
  • Standalone模式,独占模式,启动应用程序时告诉调度器需要多少资源(多少CPU线程,多少内存),在应用程序的完整生命周期内,独享资源,哪怕资源闲置也无法分配给其他用户使用;
  • Yarn模式,Yarn模式本质功能和Standalone没有太大差别,只不过YARN是Hadoop生态系统中的重要组件,有着更好的多租户的概念,且有很多其他组件对于YARN的支持很完备,Spark为了和其他Hadoop生态组件共存,支持YARN模式也是大势所趋;好在YARN发展出了Dynamic Resource Allocation(动态资源分配),让类似Spark这种大数据计算框架,在部分计算任务结束时就能提前释放资源,让给同一集群中的其它用户或者程序使用,极大提高了系统资源利用率。
  • Mesos模式,由于和Spark师出同门,Spark在很早期就已经支持它了,当然Mesos在动态资源分配上也做得比YARN要早,并且支持容器技术,当初的理念是很先进的。
  • K8s模式,确实Mesos当初的理念是很先进的,但是架不住Google力推Kubernetes,K8s在容器编排和软件生态建设上,充分发挥了google工程师团队强大的工程能力,Mesos能提供的功能,基本K8s都能给。

为什么容器技术(和K8s)对于Spark很重要?

  • 在共享云计算平台多租户的应用场景下,每个用户都希望有独立的,隔离的应用环境,减少彼此调用都干扰,比如做Python调用时,不同用户对于Python的版本可能都会有不同要求。容器技术可以为不同用户和应用构建完全隔离,独立,可简单维护的运行环境。这是Hadoop时代的YARN,利用虚拟的资源分配技术所满足不了的。

  • YARN是大数据资源调度框架,而数据中心软件系统往往还包括数据库服务,web服务,消息服务等等其它应用程序,让这些完全不相干的应用友好共存,最大化资源利用率,是数据中心维护者的最大心愿,K8s碰巧又是可以完成这一使命的有力候选人。可以参见https://www.slidestalk.com/s/SparkonKubernetesforQcon_rev213672

Spark on K8S面临的问题和调整

作为最为流行的大数据计算框架Apache Spark,与Kubernetes的集成是成为当前比较热门的话题,这个工作目前是有Google的工程师在力推。除了计算需求以外,大数据还会有大量的数据存储在HDFS之上,当Kubernetes可以轻易调度Apache Spark,为它提供一个安全可靠的运行隔离环境时,数据在多用户之间的安全性又变得非常棘手,本文正是探讨了两个话题:如何利用Kerboros这样的认证体系,打通HDFS和Spark的作业执行的权限控制;出于性能的考虑,在Spark的Executor Pod如何仍然保持数据本地性调度优化。

具体细节可以参见示说网上的ppt文档: https://www.slidestalk.com/s/apache_spark_on_k8s_and_hdfs_security

基于k8s、docker、jenkins构建springboot服务

Jenkins + github + docker + k8s + springboot

本文介绍基于k8s、docker、jenkins、springboot构建docker服务。

环境准备

server-1 k8s-master Centos7 ip地址10.12.5.110server-2 k8s-node Centos7 ip地址10.12.5.115

两台服务执行如下命令

$ setenforce 0$ systemctl stop firewalld$ systemctl disable firewalld

server-1 k8s-master 安装

k8s-master节点安装kubernets、docker、etcd、git、maven等软件。

安装docker

$ yum install docker// 修改docker配置文件 /etc/sysconfig/dockerOPTIONS='--registry-mirror=https://docker.mirrors.ustc.edu.cn --selinux-enabled --log-driver=journald --signature-verification=false'ADD_REGISTRY='--add-registry 10.12.5.110:5000'INSECURE_REGISTRY='--insecure-registry 10.12.5.110:5000'$ systemctl enable docker$ service docker start

安装registry

$ docker pull registry$ mkdir -p /data/docker/registry$ docker run -d -p 5000:5000 -v /data/docker/registry:/var/lib/registry registry

安装etcd

$ yum install etcd$ vi /etc/etcd/etcd.conf// 修改如下配置ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"$ systemctl enable etcd$ systemctl start etcd

安装kubernetes

$ yum install kubernetes// 修改配置文件apiserver$ vi /etc/kubernetes/apiserver// 修改如下配置KUBE_API_ADDRESS="--insecure-bind-address=0.0.0.0"KUBE_API_PORT="--port=8080"KUBELET_PORT="--kubelet-port=10250"KUBE_ADMISSION_CONTROL="--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ResourceQuota"// 启动服务$ systemctl enable kube-apiserver kube-controller-manager kube-scheduler$ systemctl start kube-apiserver kube-controller-manager kube-scheduler

安装git和mvn

$ yum install git$ yum install maven

server-2 k8s-node 安装
k8s-master节点安装kubernets、docker等软件。

安装docker

$ yum install docker// 修改docker配置文件 /etc/sysconfig/dockerADD_REGISTRY='--add-registry 10.12.5.110:5000'INSECURE_REGISTRY='--insecure-registry 10.12.5.110:5000'$ systemctl enable docker$ systemctl start docker

安装kubernetes

$ yum install kubernetes// 修改配置文件kubelet$ vi /etc/kubernetes/kubelet// 修改如下配置KUBELET_ADDRESS="--address=0.0.0.0"KUBELET_PORT="--port=10250"KUBELET_HOSTNAME="--hostname-override=10.12.5.115"KUBELET_API_SERVER="--api-servers=http://10.12.5.110:8080"// 修改配置文件config$ vi /etc/kubernetes/config// 修改如下配置KUBE_MASTER="--master=http://10.12.5.110:8080"// 启动服务$ systemctl enable kube-proxy kubelet$ systemctl start kube-proxy kubelet

检查k8s集群运行情况

// 在k8s-master节点执行$ kubectl get nodesNAME          STATUS    AGE10.12.5.115   Ready     3m

在k8s-master节点builder springcloudenv镜像

* 下载jdk8$ mdkir -p /home/docker/docker-jdk8$ wget http://javadl.oracle.com/webapps/download/AutoDL?BundleId=233162_512cd62ec5174c3487ac17c61aaa89e8 -O jre-8u161-linux-x64.tar.gz$ tar zxvf jre-8u161-linux-x64.tar.gz* 删除jdk中用不到的文件,尽量减少docker镜像文件大小$ cd jre1.8.0_171$ rm -rf lib/plugin.jar lib/ext/jfxrt.jar bin/javaws lib/javaws.jar lib/desktop plugin lib/deploy* lib/*javafx* lib/*jfx* lib/amd64/libdecora_sse.so lib/amd64/libprism_*.so lib/amd64/libfxplugins.so lib/amd64/libglass.so lib/amd64/libgstreamer-lite.so lib/amd64/libjavafx*.so lib/amd64/libjfx*.so* 编写Dockerfile文件$ cd /home/docker/docker-jdk8$ vi DockerfileFROM centosMAINTAINER by wangtwRUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&     echo 'Asia/Shanghai' >/etc/timezone &&     yum -y install kde-l10n-Chinese &&     localedef -c -f UTF-8 -i zh_CN zh_CN.utf8 &&     mkdir -p /usr/javaCOPY jre1.8.0_171 /usr/java/jre1.8.0_171ENV LANG zh_CN.UTF-8ENV JAVA_HOME /usr/java/jre1.8.0_171ENV PATH $JAVA_HOME/bin:$PATH* 创建镜像springcloudenv,并上传到registry,确保docker registry已经启动$ docker build -t springcloudenv .$ docker tag springcloudenv springcloudenv:v1$ docker push springcloudenv:v1// 查看registry中的镜像$ curl http://10.12.5.110:5000/v2/_catalog{"repositories":["springcloudenv"]}

安装nfs

在k8s-master节点安装nfs server

$ yum install -y nfs-utils rpcbind$ mkdir -p /data/mysql-pv$ chmod 777 /data/mysql-pv/$ mkdir -p /data/nfs$ chmod 666 /data/nfs/$ vi /etc/exports// 增加如下行/data/nfs 10.12.0.0/16(rw,no_root_squash,no_all_squash,sync)/data/mysql-pv 10.12.0.0/16(rw,no_root_squash,no_all_squash,sync)$ systemctl enable rpcbind nfs$ systemctl start rpcbind nfs$ mkdir -p /data/mysql$ mount -t nfs 10.12.5.110:/data/nfs /data/mysql/ -o proto=tcp -o nolock

在k8s-node节点安装nfs

$ yum install -y nfs-utils$ mkdir -p /data/mysql$ mount -t nfs 10.12.5.110:/data/nfs /data/mysql/ -o proto=tcp -o nolock

安装mysql docker

以持久化方式运行myql

在k8s-master节点创建相关文件

$ mkdir -p /home/k8s/yml/services/mysql$ cd /home/k8s/yml/services/mysql$ vi mysql-pv.yamlapiVersion: v1kind: PersistentVolumemetadata:    name: mysql-pvspec:    accessModes:    - ReadWriteOnce    capacity:    storage: 1Gi    persistentVolumeReclaimPolicy: Retain#storageClassName: nfs    nfs:    path: /data/mysql-pv    server: 10.12.5.110$ vi mysql-pvc.yamlapiVersion: v1kind: PersistentVolumeClaimmetadata:    name: mysql-pvcspec:  accessModes:    - ReadWriteOnce  resources:    requests:      storage: 1Gi  #storageClassName: nfs$ vi mysql.yamlapiVersion: v1kind: Servicemetadata:  name: mysqlspec:  ports:  - port: 3306  selector:    app: mysql---apiVersion: extensions/v1beta1kind: Deploymentmetadata:  name: mysqlspec:  replicas: 1  selector:    matchLabels:      app: mysql  template:    metadata:      labels:        app: mysql    spec:      containers:      - name: mysql        image: mysql:5.6        env:        - name: MYSQL_ROOT_PASSWORD          value: password        ports:        - containerPort: 3306          name: mysql        volumeMounts:         - name: mysql-persistent-storage          mountPath: /var/lib/mysql      volumes:      - name: mysql-persistent-storage        persistentVolumeClaim:          claimName: mysql-pvc

创建mysql service和pod

$ cd /home/k8s/yml/services/mysql$ kubectl create -f mysq-pv.yaml$ kubectl get pvNAME       CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM               REASON    AGEmysql-pv   1Gi        RWO           Retain          Bound     default/mysql-pvc             2d$ kubectl create -f mysq-pvc.yaml$ kubectl get pvcNAME        STATUS    VOLUME     CAPACITY   ACCESSMODES   AGEmysql-pvc   Bound     mysql-pv   1Gi        RWO           2d$ kubectl create -f mysq.yaml$ kubectl get serviceNAME         CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGEmysql        10.254.151.113   <none>        3306/TCP   2d$ kubectl get podNAME                           READY     STATUS             RESTARTS   AGEmysql-3827607452-hd5ct         1/1       Running            80         19h

k8s-master节点安装和配置jenkins

$ mkdir /home/jenkins$ cd$ vi .bash_profileexport JENKINS_HOME=/home/jenkins$ cd /home/jenkins$ wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war$ java -jar jenkins.war --httpPort=9090&浏览器打开地址:http://10.12.5.110:9090把文件/home/jenkins/secrets/initialAdminPassword的内容复制到安装页面解决插件安装离线问题打开地址:http://10.12.5.110:9090/pluginManager/advanced把https://updates.jenkins.io/update-center.json修改为http://updates.jenkins.io/update-center.json安装插件:Publish Over SSH

创建jenkins任务,源代码在github上

* 任务名称:springboottest* 任务类型:自由风格* 创建github访问CredentialCredential类型:username with password* 源码管理 选择gitRepository URL 值为https://github.com/wangtiewu/springboottest.gitCredentials 值为上一步创建的 Credential* 创建构建脚本mvn clean packagetag=$( date +'%Y%m%d%H%M' )app=springcloudtestecho $tag > version_$app.txtdocker_name=$appdocker build -t $docker_name:$tag .docker push $docker_name:$tag* 创建ssh server系统管理-〉系统设置菜单-〉publish over ssh* 增加构建后操作,Send build artifacts over SSHSSH Server:name 值为k8s masterTransfer Set:Source files 值为version_springcloudtest.txtTransfer Set:Remote directory   值为springcloudtestTransfer Set:Exec command 值为cd /home/k8s/yml/services/springcloudtestapp=springcloudtestdeploy_file=$app.yamlversion_file=version_$app.txtpatch_file=$app.patchnew_version=$( cat $version_file )old_version=$( cat $deploy_file | grep image | awk -F ":" '{print $3}' )sed -i "s/$old_version/$new_version/" $deploy_filedeployment_num=$( /usr/bin/kubectl get deployment | grep springcloudtest | wc -l )if [ $deployment_num -eq 1 ];then    p_old_version=$( cat $patch_file | jq '.spec.template.spec.containers[0].image' )    p_new_version="$app:$new_version"    sed -i "s/$p_old_version/$p_new_version/" $patch_file    patch=$( cat $patch_file )    /usr/bin/kubectl patch deployment $app -p $patchelse    /usr/bin/kubectl create -f $deploy_filefi

创建springclouttest service和pod

$ mkdir -p /home/k8s/yml/services/springcloudtest$ cd /home/k8s/yml/services/springcloudtest$ vi springcloudtest-svr.yamlapiVersion: v1kind: Servicemetadata:  name: springcloudtestspec:  ports:  - name: springcloudtest-svr    port: 9091    nodePort: 30000    targetPort: 9091  selector:    app: springcloudtest  type: NodePort$ vi springcloudtest.yamlapiVersion: extensions/v1beta1kind: Deploymentmetadata:  name: springcloudtestspec:  replicas: 1  template:    metadata:      labels:        app: springcloudtest    spec:      containers:      - name: springclouttest        image: springcloudtest:201805272108        ports:         - containerPort: 9091          protocol: TCP$ vi springcloudtest.patch{"spec":{"template":{"spec":{"containers":[{"name":"springclouttest","image":"springcloudtest:201805052039"}]}}}}

构建jenkins任务:springclouttest

构建完成后,kubectl get pod 查看springcloudtest-1053936621-w19sh   1/1       Running   1          1d

创建springclouttest service

$ cd /home/k8s/yml/services/springcloudtest$ kubectl create -f springcloudtest-svr.yaml查看服务状态$ kubectl get servicespringcloudtest   10.254.126.13   <nodes>       9091:30000/TCP   45m在k8s-node查看容器情况$ docker psCONTAINER ID        IMAGE                                                        COMMAND                  CREATED             STATUS              PORTS               NAMES269366fa1b49        mysql:5.6                                                    "docker-entrypoint..."   About an hour ago   Up About an hour                        k8s_mysql.affc4af4_mysql-3827607452-lp1cp_default_15ace557-60ed-11e8-aa6f-0800276c68b7_404d6f6a2ff21fb61984        springcloudtest:201805272108                                 "/bin/sh -c 'java ..."   About an hour ago   Up About an hour                        k8s_springclouttest.c0ee0b5d_springcloudtest-1053936621-w19sh_default_a320f883-61b2-11e8-aa6f-0800276c68b7_a99e1b3788e9ba47faf1        registry.access.redhat.com/rhel7/pod-infrastructure:latest   "/usr/bin/pod"           About an hour ago   Up About an hour                        k8s_POD.389b0ddb_mysql-3827607452-lp1cp_default_15ace557-60ed-11e8-aa6f-0800276c68b7_7387fdc6130a60acd94d        registry.access.redhat.com/rhel7/pod-infrastructure:latest   "/usr/bin/pod"           About an hour ago   Up About an hour                        k8s_POD.2aaa0bac_springcloudtest-1053936621-w19sh_default_a320f883-61b2-11e8-aa6f-0800276c68b7_fa8aba88解决 NodePort方式下,Node所在节点可以访问服务,节点外客户端请求服务都会失败问题$ iptables -P FORWARD ACCEPT

创建数据库和表

$ docker exec -it 269366fa1b49 /bin/bash$ mysql -uroot -p输入密码mysql>create database test1 default charset utf8 COLLATE utf8_general_ci;mysql>use test1mysql>create table HELLO(ID bigint, NAME varchar(128), primary key (ID));

测试服务

浏览器打开 http://10.12.5.115:30000/浏览器显示 Hello World!$ curl --data '{"id":1,"name":"Hello World"}' -H "Content-type: Application/json" http://10.12.5.115:30000/create/hello

k8s master 节点和 node 节点 启动分析

Master 节点

  • coredns 2 个 ,对应的pause 容器 2 个
  • flannel 1 个 ,对应的pause 容器 1 个
  • proxy 1 个 ,对应的pause 容器 1 个
  • scheduler 1 个 ,对应的pause 容器 1 个
  • controller 1 个 ,对应的pause 容器 1 个
  • apiserver 1 个 ,对应的pause 容器 1 个
  • etcd 1 个 ,对应的pause 容器 1 个

未分类

master 节点 端口监听情况

kubelet TCP:10248
kubelet TCP: 42006
TCP6 10250

kube-proxy TCP 10249
TCP6 10256

kube-scheduler TCP: 10251

etcd TCP: 2379
etcd TCP: 2380

kube-controller TCP: 10252
TCP6 10257

kube-apiserver TCP6:6443

未分类

Node 节点

默认启动flannel ,proxy两个容器和它们对应的pause 容器,总共是四个

未分类

Node 节点 端口监听情况

kubelet TCP 41895
TCP 10248
TCP6 10250

kube-proxy TCP 10249
TCP6 10256

未分类

Kubernetes是什么?为什么也称为K8S?

这是一篇 Kubernetes 的概览。Kubernetes 是一个自动化部署、伸缩和操作应用程序容器的开源平台。

使用 Kubernetes,你可以快速、高效地满足用户以下的需求:

  • 快速精准地部署应用程序

  • 即时伸缩你的应用程序

  • 无缝展现新特征

  • 限制硬件用量仅为所需资源

目标是培育一个工具和组件的生态系统,以减缓在公有云或私有云中运行的程序的压力。

Kubernetes的优势

  • 可移动: 公有云、私有云、混合云、多态云

  • 可扩展: 模块化、插件化、可挂载、可组合

  • 自修复: 自动部署、自动重启、自动复制、自动伸缩

Google 公司于 2014 年启动了 Kubernetes 项目。Kubernetes 是在 Google 的长达 15 年的成规模的产品级任务的经验下构建的,结合了来自社区的最佳创意和实践经验。

为什么选择容器?

想要知道你为什么要选择使用容器?

未分类

程序部署的传统方法是指通过操作系统包管理器在主机上安装程序。这样做的缺点是,容易混淆程序之间以及程序和主机系统之间的可执行文件、配置文件、库、生命周期。为了达到精准展现和精准回撤,你可以搭建一台不可变的虚拟机镜像。但是虚拟机体量往往过于庞大而且不可转移。

容器部署的新的方式是基于操作系统级别的虚拟化,而非硬件虚拟化。容器彼此是隔离的,与宿主机也是隔离的:它们有自己的文件系统,彼此之间不能看到对方的进程,分配到的计算资源都是有限制的。它们比虚拟机更容易搭建。并且由于和基础架构、宿主机文件系统是解耦的,它们可以在不同类型的云上或操作系统上转移。

正因为容器又小又快,每一个容器镜像都可以打包装载一个程序。这种一对一的“程序 – 镜像”联系带给了容器诸多便捷。有了容器,静态容器镜像可以在编译/发布时期创建,而非部署时期。因此,每个应用不必再等待和整个应用栈其它部分进行整合,也不必和产品基础架构环境之间进行妥协。在编译/发布时期生成容器镜像建立了一个持续地把开发转化为产品的环境。相似地,容器远比虚拟机更加透明,尤其在设备监控和管理上。这一点,在容器的进程生命周期被基础架构管理而非被容器内的进程监督器隐藏掉时,尤为显著。最终,随着每个容器内都装载了单一的程序,管理容器就等于管理或部署整个应用。

容器优势总结

  • 敏捷的应用创建与部署:相比虚拟机镜像,容器镜像的创建更简便、更高效。

  • 持续的开发、集成,以及部署:在快速回滚下提供可靠、高频的容器镜像编译和部署(基于镜像的不可变性)。

  • 开发与运营的关注点分离:由于容器镜像是在编译/发布期创建的,因此整个过程与基础架构解耦。

  • 跨开发、测试、产品阶段的环境稳定性:在笔记本电脑上的运行结果和在云上完全一致。

  • 在云平台与OS上分发的可转移性:可以在 Ubuntu、RHEL、CoreOS、预置系统、Google 容器引擎,乃至其它各类平台上运行。

  • 以应用为核心的管理:从在虚拟硬件上运行系统,到在利用逻辑资源的系统上运行程序,从而提升了系统的抽象层级。

  • 松散耦联、分布式、弹性、无拘束的微服务:整个应用被分散为更小、更独立的模块,并且这些模块可以被动态地部署和管理,而不再是存储在大型的单用途机器上的臃肿的单一应用栈。

  • 资源隔离:增加程序表现的可预见性。

  • 资源利用率:高效且密集。

为什么我需要 Kubernetes,它能做什么?

至少,Kubernetes 能在实体机或虚拟机集群上调度和运行程序容器。而且,Kubernetes 也能让开发者斩断联系着实体机或虚拟机的“锁链”,从以主机为中心的架构跃至以容器为中心的架构。该架构最终提供给开发者诸多内在的优势和便利。Kubernetes 提供给基础架构以真正的以容器为中心的开发环境。

Kubernetes 满足了一系列产品内运行程序的普通需求,诸如:

  • 协调辅助进程,协助应用程序整合,维护一对一“程序 – 镜像”模型。

  • 挂载存储系统

  • 分布式机密信息

  • 检查程序状态

  • 复制应用实例

  • 使用横向荚式自动缩放

  • 命名与发现

  • 负载均衡

  • 滚动更新

  • 资源监控

  • 访问并读取日志

  • 程序调试

  • 提供验证与授权

以上兼具平台即服务(PaaS)的简化和基础架构即服务(IaaS)的灵活,并促进了在平台服务提供商之间的迁移。

Kubernetes 是一个什么样的平台?

虽然 Kubernetes 提供了非常多的功能,总会有更多受益于新特性的新场景出现。针对特定应用的工作流程,能被流水线化以加速开发速度。特别的编排起初是可接受的,这往往需要拥有健壮的大规模自动化机制。这也是为什么 Kubernetes 也被设计为一个构建组件和工具的生态系统的平台,使其更容易地部署、缩放、管理应用程序。

标签(label)可以让用户按照自己的喜好组织资源。 注释(annotation)让用户在资源里添加客户信息,以优化工作流程,为管理工具提供一个标示调试状态的简单方法。

此外,Kubernetes 控制面板是由开发者和用户均可使用的同样的 API 构建的。用户可以编写自己的控制器,比如 调度器(scheduler),使用可以被通用的命令行工具识别的他们自己的 API。

这种设计让大量的其它系统也能构建于 Kubernetes 之上。

Kubernetes 不是什么?

Kubernetes 不是传统的、全包容的平台即服务(Paas)系统。它尊重用户的选择,这很重要。

Kubernetes:

  • 并不限制支持的程序类型。它并不检测程序的框架 (例如,Wildfly),也不限制运行时支持的语言集合 (比如, Java、Python、Ruby),也不仅仅迎合 12 因子应用程序,也不区分 应用 与 服务 。Kubernetes 旨在支持尽可能多种类的工作负载,包括无状态的、有状态的和处理数据的工作负载。如果某程序在容器内运行良好,它在 Kubernetes 上只可能运行地更好。

  • 不提供中间件(例如消息总线)、数据处理框架(例如 Spark)、数据库(例如 mysql),也不把集群存储系统(例如 Ceph)作为内置服务。但是以上程序都可以在 Kubernetes 上运行。

  • 没有“点击即部署”这类的服务市场存在。

  • 不部署源代码,也不编译程序。持续集成 (CI) 工作流程是不同的用户和项目拥有其各自不同的需求和表现的地方。所以,Kubernetes 支持分层 CI 工作流程,却并不监听每层的工作状态。

  • 允许用户自行选择日志、监控、预警系统。( Kubernetes 提供一些集成工具以保证这一概念得到执行)

  • 不提供也不管理一套完整的应用程序配置语言/系统(例如 jsonnet)。

  • 不提供也不配合任何完整的机器配置、维护、管理、自我修复系统。

另一方面,大量的 PaaS 系统运行在 Kubernetes 上,诸如 Openshift、Deis,以及 Eldarion。你也可以开发你的自定义PaaS,整合上你自选的CI系统,或者只在 Kubernetes 上部署容器镜像。

因为 Kubernetes 运营在应用程序层面而不是在硬件层面,它提供了一些 PaaS 所通常提供的常见的适用功能,比如部署、伸缩、负载平衡、日志和监控。然而,Kubernetes 并非铁板一块,这些默认的解决方案是可供选择,可自行增加或删除的。

而且, Kubernetes 不只是一个编排系统 。事实上,它满足了编排的需求。 编排 的技术定义是,一个定义好的工作流程的执行:先做 A,再做 B,最后做 C。相反地, Kubernetes 囊括了一系列独立、可组合的控制流程,它们持续驱动当前状态向需求的状态发展。从 A 到 C 的具体过程并不唯一。集中化控制也并不是必须的;这种方式更像是编舞。这将使系统更易用、更高效、更健壮、复用性、扩展性更强。

Kubernetes 这个单词的含义?k8s?

Kubernetes 这个单词来自于希腊语,含义是 舵手 或 领航员 。其词根是 governor 和 cybernetic。 K8s 是它的缩写,用 8 字替代了“ubernete”。

『中级篇』k8s基础网络Cluster Network

通过国人大神的一键安装k8s集群安装了3个master节点和3个node节点的虚机,现在咱们的演示直接在开发机上运行就可以了dev机器。这次主要说说k8s相关的网络和网络相关的概念,service!源码:https://github.com/limingios/docker/tree/master/No.10

准备工作 创建2个pod

进入dev虚机上

cd ~
cd deployk8s-master/
cd pod-basic/
kubectl create -f pod_nginx.yml
kubectl create -f pod_tomcat.yml

未分类

未分类

未分类

进入node节点

  • 21,22,23 分别ping下pod的节点看能否ping通

ping nginx 都是可以ping通的

未分类

ping tomcat 都是可以ping通的

未分类

说明pod节点直接都是互相通信的

进入这3个node节点发现

他们都使用了Flannel的网络

未分类

未分类

未分类

详细看看官网怎么说

https://kubernetes.io/docs/concepts/cluster-administration/networking/

  • all containers can communicate with all other containers without NAT所有的容器和其他所有的容器之间可以直接通信,不需要经过NAT的转化

  • all nodes can communicate with all containers (and vice-versa) without NAT
    所有的节点可以直接访问其他节点的容器。the

  • IP that a container sees itself as is the same IP that others see it as
    容器自己的ip是什么别人直接访问他的时候就可以用这个ip地址。

k8s开源社区的插件太多了,支持插件的的,很早以前docker是不支持网络插件的,k8s的网络插件可以更方便的打通容器和节点。

flannel主要提供了跨主机间的容器通信;

  1. 在kubernetes的Pod、Service模型里,kube-proxy又借助iptables实现了Pod和Service间通信。
  2. 基于这种网络访问功能,我们平台提供了以下功能:
  3. 基于gorouter提供的平台域名的访问 – watch k8s endpoints event管理router信息;
  4. 基于skydns并定制化kube2sky组件和kubelet,提供同一命名空间下应用(Pod)之间基于业务域名的访问 – kube2sky基于k8s Service annotation解析并注册域名信息、kubelet设置容器启动时的domain search及外部dns;
  5. 实现容器tty访问控制台 – 每台k8s node部署平台组件 tty agent(根据Pod所属node信息, 建立对应k8s结点的tty连接);

PS:基础网络方便的通信使用k8s提供的满足它要求的插件。下节我们一起坐下如何让pod可以外部来访问。

超适合小项目的 K8S 部署策略

Kubernetes 的稳健性、可靠性使它成为现阶段最流行的云原生技术之一,但也有不少用户反映, Kubernetes 技术学习起来十分复杂,只适用于大集群且成本较高。这篇文章将打破你的观念,教你在小型项目中部署 Kubernetes 集群。

选择 K8S 部署小型集群的三大理由

理由一:花费时间少

在部署小型集群之前,你需要思考以下这些问题:

  • 应该如何部署应用程序?(仅仅 rsync 到服务器?)

  • 依赖关系是怎么样的?(如果利用 python 或 ruby,你必须在服务器上安装它们!)

  • 手动运行命令?(如果以 nohup 的方式在后台运行二进制文件这可能不是最好的选择,但去配置路由服务,是否还需要学习 systemd?)

  • 如何通过不同域名或 HTTP 路径运行多个应用程序?(你可能需要设置 haproxy 或 Nginx!)

  • 当更新应用程序后应该如何推出新变化?(停止服务、部署代码、重启服务?如何避免停机?)

  • 如果搞砸了部署怎么办?有什么方法可以回滚?

  • 应用程序是否需要使用其他服务?又该如何配置这些服务?(如:redis)

以上这些问题很有可能在你部署小型集群时出现,但 Kubernetes 为上述所有问题都提供了解决方案。或许还有其他方法可以解决上述问题,但是利用 Kubernetes 往往事半功倍,因为我们需要更多的时间专注于应用程序。

理由二:Kubernetes 记录整个部署过程

让我们看看利用 Kubernetes 部署集群的第二个理由。

你在工作时是否也是这样的状态:我上次运行了什么命令?当时服务器在运行什么服务?这让我想到了著名的 bash.org:

<erno> hm. I've lost a machine.. literally _lost_. it responds to ping, it works completely, I just can't figure out where in my apartment it is.

                                                                                                                            http://bash.org/?5273

这种情况曾经出现在我的工作中,让原本 10 分钟的工作量变成了一个周末。

但是如果你选择 Kubernetes 部署集群,就不会有这种困扰。因为 Kubernetes 使用描述性格式,如此用户就可以很轻松地知道接下来应该运行哪些内容,如何部署构建块。此外,控制层也会正常处理节点故障并自动重新调度 Pod。(对于像 Web 应用程序这样的无状态服务,就不再需要担心失败。)

理由三:Kubernetes 简单易学

Kubernetes 拥有自己的词汇表、工具,以及与传统 Unix 完全不同的配置服务器。Kubernetes 的知识足以建立和维护基础设施。使用 Kubernetes,你可以完全可以在 Kubernetes 中配置服务,无需 SSH 到服务器。你不必学习 systemd 也不必知道什么是运行级别; 你不必格式化磁盘,或学习如何使用 ps,vim。

我通过一个例子,来证明我的观点!

这是利用 Unix :

[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network.target remote-fs.target nss-lookup.target[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target

真的比利用 Kubernetes 难:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 1
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80

当你远程执行基础架构管理时,是不需要手动维护服务器的。你可以使用:ansible、salt、chef、puppet 等来完成这件事。当然,你需要学习使用很多 Kubernetes 相关工具,这要比学习替代品轻松的多。

小 结

Kubernetes 并不是将一件事做到极致的工具而是一个全方位的解决方案,它取代了开发人员习惯使用的许多技术和工具。

接下来我们用实际操作,为大家部署一个小型 Kubernetes 集群。

建立小型 Kubernetes 集群

下面就开始我们的教程。对于这个例子,我们将使用谷歌的 Kubernetes引擎(GKE),但如果谷歌不是你的菜,你也可以选择亚马逊(EKS)或微软(AKS)。

要构建我们的 Kubernetes 集群,我们将需要:

  • 域名(10 美元 /年,具体取决于域名);

  • DNS 主机由 cloudflare 提供(免费);

  • GKE 中的 3 个 node kubernetes 集群(5 美元 / 月);

  • 将 Webapp 作为 Docker 容器发送到 Google Container Registry(GCR)(免费);

  • 一些 yaml 文件配置 Kubernetes。

此外,为了节省成本,我们不会使用谷歌的 ingress controller 。相反,我们将在每个节点上运行 Nginx 作为 Daemon,并构建一个自定义运算符,将工作节点外部 IP 地址与 Cloudflare 同步。

谷歌设置

首先访问 console.cloud.google.com 并创建一个项目(如果你还没有项目)。你还需要设置结算帐户。然后前往 hamburger 菜单中的 Kubernetes 页面并创建一个新的集群。

你需要执行以下操作:

  • 选择 Zonal 区域类型(我使用了 us-central1-a 作为我的区域);

  • 选择你的 Kubernetes 版本;

  • 使用最便宜的实例类型(f1-micro)创建 3 个 node 池;

  • 对于该节点池,在高级屏幕中,将引导磁盘大小设置为 10GB,启用可抢占的 node(它们更便宜),启用自动升级和自动修复;

  • 在节点池下面还有一些其他选项。我们想要禁用 HTTP 负载均衡(GCP 中的负载均衡很昂贵且不稳定)并且还禁用所有 StackDriver 的服务以及禁用 Kubernetes dashboard 。

通过设置所有这些选项,你可以继续创建集群。

以下是减少的成本:

  • Kubernetes 控制层:免费,因为谷歌不收取专家的费用;

  • Kubernetes 工作节点:5.04 美元/月,3 个微节点通常为 11.65 美元/月,通过使用它们的可抢占性,我们将其降至 7.67 美元/月(“永久免费”等级则达到 5.04 美元);

  • 存储成本:免费,存储成本可以在 GCP 中累计。我们将免费获得 30GB 的永久磁盘,这就是我们选择 10GB 大小的原因;

  • 负载均衡器成本:免费,我们禁用 HTTP 负载均衡,因为仅此一项费用将达到 18 美元/月。相反,我们将在每个节点上运行我们自己的 HTTP 代理,并将 DNS 指向公共 IP;

  • 网络费用:免费,只要你每月低于 1GB,出口就是免费的(之后每GB 8 美分)。

因此,我们可以拥有一个 3 个节点的 Kubernetes 集群,价格与单个数字机器相同。

除了设置 GKE 之外,我们还需要添加一些防火墙规则,以允许外网点击我们节点上的 HTTP 端口。操作是:从 hamburger 菜单转到 VPC 网络,防火墙规则添加为 TCP 端口 80 和 443 的规则,IP 范围为 0.0.0.0/0。

未分类

本地设置

随着集群的启动和运行,我们就可以对其进行配置。通过 cloud.google.com/sdk/docs 的说明安装 gcloud 工具。

安装完成后,你可以通过运行以下命令进行设置:

gcloud auth login

你还需安装 Docker,将其连接到 GCR 上,方便你进行容器推送:

gcloud auth configure-docker

你也可以按照此处的说明安装和设置 kubectl (此工具适用于 Windows,OSX 或 Linux)。

gcloud components install kubectl
gcloud config set project PROJECT_ID
gcloud config set compute/zone COMPUTE_ZONE
gcloud container clusters get-credentials CLUSTER_NAME

构建 Web 应用程序

你可以使用任何编程语言构建 Web 应用。我们只需构建一个 port 端口的 HTTP 应用程序。就个人而言,我更喜欢在 Go 中构建这些应用程序,但对于某些类型,让我们尝试使用 Crystal。

创建一个 main.cr 文件:

# crystal-www-example/main.crrequire "http/server"Signal::INT.trap do
  exit
end

server = HTTP::Server.new do |context|
  context.response.content_type = "text/plain"
  context.response.print "Hello world from crystal-www-example! The time is #{Time.now}"end

server.bind_tcp("0.0.0.0", 8080)
puts "Listening on http://0.0.0.0:8080"server.listen

我们还需要一个 Dockerfile:

# crystal-www-example/Dockerfile
FROM crystallang/crystal:0.26.1 as builder

COPY main.cr main.cr

RUN crystal build -o /bin/crystal-www-example main.cr --release

ENTRYPOINT [ "/bin/crystal-www-example" ]

我们可以通过以下命令来构建和测试我们的 Web 应用程序:

docker build -t gcr.io/PROJECT_ID/crystal-www-example:latest .
docker run -p 8080:8080 gcr.io/PROJECT_ID/crystal-www-example:latest

然后输入 localhost:8080 在浏览器中访问。接着我们可以通过以下方式将我们的应用程序推到 GCR 中运行:

docker push gcr.io/PROJECT_ID/crystal-www-example:latest

配置 Kubernetes

对于此示例,我们将创建几个 yaml 文件来表示各种服务,然后通过运行 kubectl apply 在集群中配置它们。Kubernetes 的配置具有描述性,这些 yaml 文件将会告诉 Kubernetes 我们希望看到的状态。

我们需要做的事情:

  • 为我们的 crystal-www-example Web 应用程序创建部署和服务;

  • 为 Nginx 创建一个 Daemon Set 和 Config Map;

  • 运行自定义应用程序使用 Cloudflare 到 DNS 同步 node IP。

Web App 配置

首先让我们配置 webapp:(先将 PROJECT_ID 替换为你的项目 ID)

# kubernetes-config/crystal-www-example.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: crystal-www-example
  labels:
    app: crystal-www-example
spec:
  replicas: 1
  selector:
    matchLabels:
      app: crystal-www-example
  template:
    metadata:
      labels:
        app: crystal-www-example
    spec:
      containers:
      - name: crystal-www-example
        image: gcr.io/PROJECT_ID/crystal-www-example:latest
        ports:
        - containerPort: 8080---

kind: Service
apiVersion: v1
metadata:
  name: crystal-www-example
spec:
  selector:
    app: crystal-www-example
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080

现在创建一个 Deployment,它会通知 Kubernetes 创建一个 Pod,其中包含一个运行 Docker 容器的容器,以及一个用于集群内的 service discovery 。要应用此配置运行(在 kubernetes-config 文件夹中):

kubectl apply -f .

我们可以使用以下方法测试它是否在运行:

kubectl get pod
# you should see something like:
# crystal-www-example-698bbb44c5-l9hj9          1/1       Running   0          5m

我们还可以创建代理 API,以便我们访问它:

kubectl proxy

然后访问:

http://localhost:8001/api/v1/namespaces/default/services/crystal-www-example/proxy/

Nginx 配置

通常,在 Kubernetes 中处理 HTTP 服务时,你会使用 ingress controller。不幸的是,Google 的 HTTP 负载均衡器非常昂贵,因此我们将运行自己的 HTTP 代理并手动配置它。

我们将使用 Daemon Set 和 Config Map。Daemon Set 是在每个节点上运行的应用程序。Config Map 基本上是一个小文件,我们可以在容器中安装它,我们将存储 Nginx 配置。

yaml 看起来像这样:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      containers:
      - image: nginx:1.15.3-alpine
        name: nginx
        ports:
        - name: http
          containerPort: 80
          hostPort: 80
        volumeMounts:
        - name: "config"
          mountPath: "/etc/nginx"
      volumes:
      - name: config
        configMap:
          name: nginx-conf

---

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-conf
data:
  nginx.conf: |
    worker_processes 1;
    error_log /dev/stdout info;

    events {
      worker_connections 10;
    }

    http {
      access_log /dev/stdout;

      server {
        listen 80;
        location / {
          proxy_pass http://crystal-www-example.default.svc.cluster.local:8080;
        }
      }
    }

你可以看到我们如何在 Nginx 容器内挂载 config map 的 nginx.conf。我们还在规范上设置了两个附加字段:hostNetwork: true、dnsPolicy: ClusterFirstWithHostNet 。

  • hostNetwork: true 以便我们可以绑定主机端口并从外网到达 Nginx;

  • dnsPolicy: ClusterFirstWithHostNet 以便我们可以访问集群内的服务。

应用更改:通过点击节点的公共 IP 来到达 Nginx。

你可以通过运行找到:

kubectl get node -o yaml
# look for:
# - address: ...
#   type: ExternalIP

我们的网络应用程序现在可通过互联网访问了。现在想想给它起一个好听的名字!

连接 DNS

我们需要 A 为集群的节点设置 3 条 DNS 记录:

未分类

然后添加一个 CNAME 条目以指向那些 A 记录。(即 www.example.com CNAME kubernetes.example.com)我们可以手动执行此操作,但最好自动执行此操作,以便在扩展或替换节点时 DNS 记录自动更新。

我认为这也是一个很好的说明示例,说明如何让 Kubernetes 为你工作而不是反对它。Kubernetes 完全可编写脚本,并且具有强大的 API。因此你可以使用不太难编写的自定义组件填补空白。我为此构建了一个小型 Go 应用程序,可在此处找到:kubernetes-cloudflare-sync。

建立一个 informer:

factory := informers.NewSharedInformerFactory(client, time.Minute)
lister := factory.Core().V1().Nodes().Lister()
informer := factory.Core().V1().Nodes().Informer()
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
  AddFunc: func(obj interface{}) {
    resync()
  },
  UpdateFunc: func(oldObj, newObj interface{}) {
    resync()
  },
  DeleteFunc: func(obj interface{}) {
    resync()
  },
})
informer.Run(stop)

informer 将在节点更改时随时调用我的 resync 函数。然后使用 Cloudflare API 库(github.com/cloudflare/cloudflare-go)同步 IP ,类似于:

var ips []stringfor _, node := range nodes {  for _, addr := range node.Status.Addresses {    if addr.Type == core_v1.NodeExternalIP {
      ips = append(ips, addr.Address)
    }
  }
}
sort.Strings(ips)for _, ip := range ips {
  api.CreateDNSRecord(zoneID, cloudflare.DNSRecord{
    Type:    "A",
    Name:    options.DNSName,
    Content: ip,
    TTL:     120,
    Proxied: false,
  })
}

就像我们的网络应用程序一样,我们将此应用程序作为 Kubernetes 中的部署:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubernetes-cloudflare-sync
  labels:
    app: kubernetes-cloudflare-sync
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kubernetes-cloudflare-sync
  template:
    metadata:
      labels:
        app: kubernetes-cloudflare-sync
    spec:
      serviceAccountName: kubernetes-cloudflare-sync
      containers:
      - name: kubernetes-cloudflare-sync
        image: gcr.io/PROJECT_ID/kubernetes-cloudflare-sync
        args:
        - --dns-name=kubernetes.example.com
        env:
        - name: CF_API_KEY
          valueFrom:
            secretKeyRef:
              name: cloudflare
              key: api-key
        - name: CF_API_EMAIL
          valueFrom:
            secretKeyRef:
              name: cloudflare
              key: email

你需要使用 cloudflare api 密钥和电子邮件地址创建 Kubernetes 密码:

kubectl create secret generic cloudflare --from-literal=email='EMAIL' --from-literal=api-key='API_KEY'

你还需要创建服务帐户(它允许我们的部署访问 Kubernetes API 来检索节点)。首次运行(特别是对于 GKE):

kubectl create clusterrolebinding cluster-admin-binding --clusterrole cluster-admin --user YOUR_EMAIL_ADDRESS_HERE

然后申请:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: kubernetes-cloudflare-sync
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: kubernetes-cloudflare-sync
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-cloudflare-sync-viewer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kubernetes-cloudflare-sync
subjects:
- kind: ServiceAccount
  name: kubernetes-cloudflare-sync
  namespace: default

配置就绪后,运行 Cloudflare 的应用程序将在任何节点更改时被更新。以上就是基于 Kubernetes 部署小型集群的详细教程。

总 结

Kubernetes 就是这样一个收放自如的技术。就像你可能永远用不到 SQL 数据库中的所有功能,但你不得不承认 SQL 数据库极大地提高了你快速交付解决方案的能力。

Kubernetes 与 SQL 十分相似。在 Kubernetes 庞大的技术体系下,我们也并不能用到所有功能,却能在每个项目中恰到好处的使用部分功能实现完美部署。在每次利用 Kubernetes 部署小型集群时,我都会从中获得新的认知。

所以我的观点是,Kubernetes 对于小型部署也很有意义,而且既易于使用又便宜。如果你从来没有尝试过,现在就开动起来吧!

END

添加iptables规则,防止s[]s回源国内

添加以下规则后,ss服务器将不能对国内IP建立连接。
只禁止出口连接,也就是服务器主动连接,ftp会受影响,入口连接正常,小鸡不会失联

iptables -A OUTPUT -m geoip --dst-cc CN  -j REJECT

TIPS:
使用上面命令前,请先按照下面教程为iptables添加geoip模块

https://daenney.github.io/2017/01/07/geoip-filtering-iptables.html

如果是debian或ubuntu,就是直接执行下面命令

apt install xtables-addons-common xtables-addons-dkms libtext-csv-xs-perl
/usr/lib/xtables-addons/xt_geoip_dl
mkdir /usr/share/xt_geoip
/usr/lib/xtables-addons/xt_geoip_build -D /usr/share/xt_geoip GeoIPCountryWhois.csv GeoIPv6.csv

记得保存和开机自动恢复iptables规则。

操作完成后,s[]s服务器就不会再访问国内服务器了。高墙也不会知道你是一台代理服务器了。

本教程需要一定的linux知识。且各个linux发行版操作方式不尽相同。责任自负。

iptables 的limit模块

Limit模块的功能是限制单位时间内进入数据包的数量。

Limit的工作原理及令牌桶算法朱双印大佬介绍的生动明晰,此地不再赘述,只用实例进一步加深Limit模块的理解。

iptables -t filter -R INPUT 1 -p icmp -m limit --limit 10/minute --limit-burst 5 -j ACCEPT

--limit 10/minute表示一分钟产生10个令牌即6秒一个,--limit-burst 5表示令牌桶最多可以放5个令牌,此项默认值就是5。Ping本机的响应信息如下:

64 bytes from 192.168.80.133: icmp_seq=0 ttl=64 time=21.3 ms

64 bytes from 192.168.80.133: icmp_seq=1 ttl=64 time=0.564 ms

64 bytes from 192.168.80.133: icmp_seq=2 ttl=64 time=0.559 ms

64 bytes from 192.168.80.133: icmp_seq=3 ttl=64 time=0.478 ms

64 bytes from 192.168.80.133: icmp_seq=4 ttl=64 time=0.488 ms

From 192.168.80.133 icmp_seq=5 Destination Port Unreachable

64 bytes from 192.168.80.133: icmp_seq=6 ttl=64 time=0.096 ms

From 192.168.80.133 icmp_seq=7 Destination Port Unreachable

From 192.168.80.133 icmp_seq=8 Destination Port Unreachable

From 192.168.80.133 icmp_seq=9 Destination Port Unreachable

From 192.168.80.133 icmp_seq=10 Destination Port Unreachable

From 192.168.80.133 icmp_seq=11 Destination Port Unreachable

64 bytes from 192.168.80.133: icmp_seq=12 ttl=64 time=0.710 ms

From 192.168.80.133 icmp_seq=13 Destination Port Unreachable

From 192.168.80.133 icmp_seq=14 Destination Port Unreachable

From 192.168.80.133 icmp_seq=15 Destination Port Unreachable

From 192.168.80.133 icmp_seq=16 Destination Port Unreachable

From 192.168.80.133 icmp_seq=17 Destination Port Unreachable

64 bytes from 192.168.80.133: icmp_seq=18 ttl=64 time=1.22 ms

因为--limit-burst 是5,所以前5个包顺利拿到令牌进入本机,第6个ping包到来时令牌桶空了,直接被拒绝。此时6秒时间已到,系统便生成一个令牌并放入桶中,此时第7个请求包到达,拿到令牌后进入本机ping成功了。第8,9,10,11,12个ping包请求时,令牌桶空,这5个包直接被拒绝。当到达6秒时又产生一个令牌,这时正好第13个ping包请求,便成功了。以后便如此反复。

把ping的间隔调到2秒,ping -i 2 192.168.80.133

64 bytes from 192.168.80.133: icmp_seq=0 ttl=64 time=51.5 ms

64 bytes from 192.168.80.133: icmp_seq=1 ttl=64 time=0.852 ms

64 bytes from 192.168.80.133: icmp_seq=2 ttl=64 time=0.630 ms

64 bytes from 192.168.80.133: icmp_seq=3 ttl=64 time=0.383 ms

64 bytes from 192.168.80.133: icmp_seq=4 ttl=64 time=0.612 ms

64 bytes from 192.168.80.133: icmp_seq=5 ttl=64 time=0.497 ms

From 192.168.80.133 icmp_seq=6 Destination Port Unreachable

64 bytes from 192.168.80.133: icmp_seq=7 ttl=64 time=1.35 ms

From 192.168.80.133 icmp_seq=8 Destination Port Unreachable

From 192.168.80.133 icmp_seq=9 Destination Port Unreachable

64 bytes from 192.168.80.133: icmp_seq=10 ttl=64 time=0.110 ms

From 192.168.80.133 icmp_seq=11 Destination Port Unreachable

From 192.168.80.133 icmp_seq=12 Destination Port Unreachable

64 bytes from 192.168.80.133: icmp_seq=13 ttl=64 time=0.567 ms

刚开始令牌桶中有5个令牌,所以前5个包可以顺利ping通,于此同时系统时间过了10秒,期间系统产生了第1个令牌,第6个包也可以ping通。第7个ping包到达时还没产生第2个令牌,icmp_seq=6包被拒绝。以后便如此反复。

把ping的间隔调到3秒,4秒,5秒等,又会如何?

--limit 10/minute的值调大或调小,又会出现什么情况,只有不断尝试不断探索才能不断进步。

浅谈iptables防SYN Flood攻击和CC攻击

————————本文为自己实践所总结,概念性的东西不全,这里粗劣提下而已,网上很多,本文主要说下目前较流行的syn洪水攻击和cc攻击————————————-

何为syn flood攻击:

SYN Flood是一种广为人知的DoS(拒绝服务攻击)是DDoS(分布式拒绝服务攻击)的方式之一,这是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,从而使得被攻击方资源耗尽(CPU满负荷或内存不足)的攻击方式(TCP协议的缺陷,所以没办法根除,除非重做TCP协议,目前不可能)。

正常原理是:

  1. TCP三次握手,客户端向服务器端发起连接的时候发送一个包含SYN标志的TCP报文,SYN即同步(Synchronize),同步报文会指明客户端使用的端口以及TCP连接的初始序号

  2. 服务器在收到客户端的SYN报文后,将返回一个SYN+ACK的报文,表示客户端的请求被接受,同时TCP序号被加一,ACK即确认(Acknowledgment),夹带也发送一个SYN包给客户端,并且服务器分配资源给该连接。

  3. 客户端也返回一个确认报文ACK给服务器端,同样TCP序列号被加一,到此一个TCP连接完成。

syn flood攻击利用TCP三次握手的缺陷,在TCP连接的第三次握手中,当服务器收到客户端的SYN包后并且返回客户端ACK+SYN包,由于客户端是假冒IP,对方永远收不到包且不会回应第三个握手包。导致被攻击服务器保持大量SYN_RECV状态的“半连接”,并且会有重试默认5次回应第二个握手 包,塞满TCP等待连接队列,资源耗尽(CPU满负荷或内存不足),让正常的业务请求连接不进来。通常SYN Flood会和ARP欺骗一起使用,这样就造成了SYN攻击。

何为CC攻击:

CC攻击(Challenge Collapsar)是DDOS(分布式拒绝服务)的一种,也是一种常见的网站攻击方法,攻击者通过代理服务器或者肉鸡(被黑客黑的电脑)向受害主机不停地发大量数据包,造成对方服务器资源耗尽,一直到宕机崩溃。CC主要是用来攻击页面的,每个人都有这样的体验:当一个网页访问的人数特别多的时候,打开网页就慢了,CC就是模拟多个用户(多少线程就是多少用户)不停地进行访问那些需要大量数据操作(就是需要大量CPU时间)的页面,造成服务器资源的浪费,CPU长时间处于100%,永远都有处理不完的连接直至就网络拥塞,正常的访问被中止。

攻击检测:

当你发现发服务器很卡,web访问很慢 甚至连SSH操作都开始有点卡的时候,你就要非常注意了。

检测可以这样做:

top 查看CPU使用率和CPU负载情况

负载一般小于CPU核数*0.7算正常,负载内等于或者稍大于核数。说明CPU负载开始严重了,如果超过,说明有问题。

看看哪些程序CPU使用率较高,是否为正常占用,可以使用 pidof 进程名 查看该进程名的所有进程号,然后ll /proc/进程号/exe、fd查看是否为正常信息。

netstat查看端口状态

netstat -n | grep "^tcp" | awk '{print $6}' | sort  | uniq -c | sort -n

1 SYN_RECV

13 FIN_WAIT1

64 TIME_WAIT
149 ESTABLISHED

     
可以查看当前连接状态的数量,从而进行判断。

还有vmstat、sar、等检测命令,网上有使用方法!

Syn Flood 一般的防御:
    
第一种:缩短SYN Timeout时间,由于SYN Flood攻击的效果取决于服务器上保持的SYN半连接数,这个值=SYN攻击的频度 x SYN Timeout,所以通过缩短从接收到SYN报文到确定这个报文无效并丢弃改连接的时间。

第二种:设置SYN Cookie,就是给每一个请求连接的IP地址分配一个Cookie,如果短时间内连续受到某个IP的重复SYN报文,就认定是受到了攻击,以后从这个IP地址来的包会被丢弃。

(缺陷:缩短SYN Timeout时间仅在对方攻击频度不高的情况下生效,SYN Cookie更依赖于对方使用真实的IP地址,如果攻击者以数万/秒的速度发送SYN报文,同时利用ARP欺骗随机改写IP报文中的源地址,以上的方法将毫无用武之地。)

vim /etc/sysctl.conf

    
增加或者修改如下:(修改保存后记得sysctl -p 使之生效)

net.ipv4.tcp_syncookies = 1

net.ipv4.tcp_fin_timeout = 1

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_max_tw_buckets = 6000

net.ipv4.tcp_tw_recycle = 1

net.ipv4.tcp_syn_retries = 1

net.ipv4.tcp_synack_retries = 1

net.ipv4.tcp_max_syn_backlog = 262144

net.core.netdev_max_backlog = 262144

net.ipv4.tcp_max_orphans = 262144

net.ipv4.tcp_keepalive_time = 30

iptables性质防御:

限制syn的请求速度(这个方式需要调节一个合理的速度值,不然会影响正常用户的请求)

iptables -N syn-flood   (新建一条链)

iptables -A INPUT -p tcp --syn -j syn-flood 

iptables -A syn-flood  -p tcp -m limit --limit 2/s --limit-burst 50 -j RETURN

iptables -A syn-flood -j DROP

  
Tips:攻击这东西只能防御不能完全根除!只能缓解,降低到最低的风险。有钱可以上第三方公司的产品服务!嘿嘿。

CC攻击一般防御:

  1. 一般而言,CC攻击都是真实的IP,所以一般的做法通俗点就是封IP

  2. 更改web端口,默认CC攻击都是攻击服务器的80端口  

  3. 域名欺骗,我们可以使用cdn等加速工具代理我们的服务器,从而实现防御,(网上有人说把域名解析到127.0.0.1 让攻击者自己攻击自己,不知道有没有用,没试过。而且正式业务,你怎么可能去做这样的解析,那业务怎么办?)

使用抓包命令,抓取访问服务器80的IP数

tcpdump -tnn dst port 80 -c 100 | awk -F"." '{print $1"."$2"."$3"."$4}' | sort | uniq -c | sort -n -r |head -20

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode

listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
100 packets captured
101 packets received by filter
  0 packets dropped by kernel
78 IP 221.239.28.142
17 IP 124.65.101.82
13 IP 14.123.162.69
 7 IP 183.238.49.188
 7 IP 120.234.19.186

可以看到221.239.28.142的包最多,所以我们进行封IP处理。

iptables -I INPUT -s 221.239.28.142 -j REJECT 

iptabes 其他限制规则:

防御太多DOS攻击连接,可以允许外网每个IP最多15个初始连接,超过的丢弃,第二条是在第一条的基础上允许已经建立的连接和子连接允许

iptables -A INPUT -i eth0 -p tcp --syn -m connlimit --connlimit-above 15 --connlimit-mask 32 -j DROP  (--connlimit-mask 32为主机掩码,32即为一个主机ip,也可以是网段)
iptables -A INPUT -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT

抵御DDOS ,允许外网最多24个初始连接,然后服务器每秒新增12个,访问太多超过的丢弃,第二条是允许服务器内部每秒1个初始连接进行转发

iptables -A INPUT  -p tcp --syn -m limit --limit 12/s --limit-burst 24 -j ACCEPT
iptables -A FORWARD -p tcp --syn -m limit --limit 1/s -j ACCEPT

允许单个IP访问服务器的80端口的最大连接数为 20

iptables -I INPUT -p tcp --dport 80 -m connlimit  --connlimit-above 20 -j REJECT 

对访问本机的22端口进行限制,每个ip每小时只能连接5次,超过的拒接,1小时候重新计算次数

iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name SSHPOOL --rcheck --seconds 3600 --hitcount 5 -j DROP

iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name SSHPOOL --set -j ACCEPT

(上面recent规则只适用于默认规则为DROP中,如果要适用默认ACCEPT的规则,需要–set放前面 并且无-j ACCEPT)

【Docker】启动container的时候出现iptables: No chain/target/match by that name

问题

Error response from daemon: driver failed programming external connectivity
 on endpoint jenkins (a8ea15bf9b3dbed599d059d638f79f9dd5e875556c39bfb41e6563d3feedb81b):
  (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 50000 -j DNAT
 --to-destination 172.18.0.6:50000 ! -i br-031aa3930383: iptables: No chain/target/match
 by that name.

光看这个报错: iptables: No chain/target/match by that name,就能够看出是跟iptables有关

原因(猜测)

如果再启动docker service的时候网关是关闭的,那么docker管理网络的时候就不会操作网管的配置(chain docker),然后网关重新启动了,导致docker network无法对新container进行网络配置,也就是没有网管的操作权限,做重启处理

处理

service docker restart
或
systemctl docker restart

使用的centos7服务器,在部署docker的过程中,因端口问题有启停firewalld服务,在centos7里使用firewalld代替了iptables。在启动firewalld之后,iptables还会被使用,属于引用的关系。所以在docker run的时候,iptables list里没有docker chain,重启docker engine服务后会被加入到iptables list里面。(有必要深入研究一下docker network)

另一个方法

关闭网关(不建议)

systemctl stop firewalld

systemctl stop iptables