holdno

holdno 查看完整档案

厦门编辑厦门大学嘉庚学院  |  电子商务 编辑xxx  |  PHP研发工程师 编辑 www.jihe.pro 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

holdno 赞了回答 · 2019-11-06

为什么在 go 中总是针对 map 这个数据类型来讨论非并发安全?

楼上回答不够准确

原因是 Go的基础类型是值传递的,这就使得很多基础类型包括struct传递给goroutine时都是copy过去的,不存在现成安全问题,如果传递指针类型,对于指针类型本身也是copy过去的,只是传递过去的是地址而已。

引用传递的只有map、slice和chan,chan本来就是用于多协程,一般不会遇到这个问题。

map比slice更容易用于不安全的线程场景。所以一般都用map为例。

另外,go还提供了线程安全的map类型 sync包,了解一下

关注 7 回答 4

holdno 回答了问题 · 2019-11-04

解决当golang调用 time.Sleep()时 调度器发生了什么 G、P和M之间又是怎样的关系?

又查阅了相关资料
自己总结一下

golang优化了time.Sleep()方法,避免其进行系统调用从而带来一些系统级别的开销
当我们在goroutine中调用time.Sleep()方法时
底层会创建一个 timer, 并将当前goroutine与timer进行绑定,并提供一个唤醒该goroutine的方法goroutineReady
然后通过addtimerLocked方法启动全局时间事件驱动器timerproc 并将timer加入到全局timer管理器中(timer 通过最小堆的数据结构存放每个定时器),且每一个加入到timer管理器中的timer都会被按唤醒时间进行排序,最早需要唤醒的timer排在最前面
再通过gopark()方法将当前goroutine状态标记为为_Gwaiting
解除G与M的绑定关系
调用schedule()函数,重新选择一个goroutine去执行

如何被唤醒呢?
timer管理器会寻找timer定时管理器的第一个timer并判断唤醒条件,满足条件后通过调用goroutineReady方法进行唤醒,主要操作是将对应的goroutine标记为_Grunnable并将其添加到P队列中等待被M执行

关注 1 回答 2

holdno 提出了问题 · 2019-11-04

解决当golang调用 time.Sleep()时 调度器发生了什么 G、P和M之间又是怎样的关系?

其实这个问题就是想了解
golang中time.Sleep是如何实现的
在这背后调度器对time.Sleep又是如何处理的?

关注 1 回答 2

holdno 发布了文章 · 2019-09-25

面向业务开放的k8s容器平台,你是否忽略了service account?

什么是service account

A service account provides an identity for processes that run in a Pod

service account:服务账户,服务账户是针对运行在pod中的进程而言的

kubernetes中账户分为两种类型,用户账户和服务账户

用户账户和服务账户

  • 用户账户是针对人而言的。 服务账户是针对运行在pod中的进程而言的。
  • 用户账户是全局性的。 其名称在集群各namespace中都是全局唯一的,未来的用户资源不会做namespace隔离, 服务账户是namespace隔离的。
  • 通常情况下,集群的用户账户可能会从企业数据库进行同步,其创建需要特殊权限,并且涉及到复杂的业务流程。 服务账户创建的目的是为了更轻量,允许集群用户为了具体的任务创建服务账户 (即权限最小化原则)。
  • 对人员和服务账户审计所考虑的因素可能不同。
  • 针对复杂系统的配置可能包含系统组件相关的各种服务账户的定义。 因为服务账户可以定制化地创建,并且有namespace级别的名称,这种配置是很轻量的。

服务账户的能力

通过上文的介绍已知,服务账户是跟随Pod进程自动创建的授权体系,通过它,使用者可以在Pod所在的namespace下为所欲为(哈哈哈
且服务账户默认是开启的
也就是说,如果是一个对外开放的k8s容器平台,且某个namespace没有设置对应的quota,或者quota足够大,那么部署在其下的业务方就可以通过k8s提供的client-gorest.InClusterConfig()方法对当前namespace下进行部署(或其他)操作,这将导致集群中出现一批不受管控的pod(或可以讲作预期之外的pod以及其他资源),作为平台方应该是不希望出现这个场景的。

好在kubernetes 1.6以上版本可以手动控制禁用此能力
引用官方介绍:

In version 1.6+, you can opt out of automounting API credentials for a service account by setting automountServiceAccountToken: false on the service account:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: build-robot
automountServiceAccountToken: false
...

In version 1.6+, you can also opt out of automounting API credentials for a particular pod:

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  serviceAccountName: build-robot
  automountServiceAccountToken: false
  ...

The pod spec takes precedence over the service account if both specify a automountServiceAccountToken value.

所以,应该在非特殊业务场景下,禁用掉automountServiceAccountToken,从而进一步保证集群的可控性

查看原文

赞 0 收藏 0 评论 0

holdno 赞了回答 · 2019-09-18

解决mysql 查询效率疑问

从来不建议用join,会有很多不可预期的问题,而且不好优化,建议分开查询,先查activity,然后查出user_ids, 然后去查user信息,merge到activity上面,就ok了,这样写好处很多,最大好处就是逻辑清晰,易于维护。 再,请慎用join

关注 5 回答 4

holdno 发布了文章 · 2019-09-18

3. 独乐乐不如众乐乐。搭桥修路,迎接用户

上篇文章有提到,通过POD ID只能够在k8s集群内部进行访问,作为一个博客!只给自己看...好像也行啊...
但是每次都要先登录到集群节点中才能看...这就...(脑海里闪过成吨shell... ssh@192.10o... kubectl get po...,噗~!
这是在玩自己吗?

言归正传

集群都有了,服务也部了,咋就不能让他与万物互联了呢?
接下来我们就一同探秘,k8s中服务是如何暴露出去的

Kubernetes 的Pod的寿命是有限的。它们出生然后死亡,它们不会复活。ReplicationController是特别用来动态的创建和销毁Pods(如:动态伸缩或者执行rolling updates中动态的创建和销毁pod)。尽管每个Pod有自己的IP地址,随着时间变化随着时间推移即使这些IP地址也不能被认为是可靠的。这带来一个问题:如果一些Pod的集合(让我们称之为backends)为集群中的其他的Pod提供了一些功能(让我们称它们为frontends),这些frontends应该如何找到并一直知道哪些backends在这样的集合中呢?

Kubernetes的Service是一种抽象,它定义了一组Pods的逻辑集合和一个用于访问它们的策略 - 有的时候被称之为微服务。
举个例子,想象一下我们的博客后端运行了三个副本。这些副本都是可以替代的 - 前端不关心它们使用的是哪一个后端。尽管实际组成后端集合的Pod可能会变化,前端的客户端却不需要知道这个变化,也不需要自己有一个列表来记录这些后端服务。Service抽象能让你达到这种解耦。

定义一个Service

Kubernetes中的Service是一个REST对象,这点与Pod类似。正如所有的REST对象一样,向apiserver POST一个Service的定义就能创建一个新的实例。以上篇文章中的echoserver为例,每一个Pod都开放了80端口,并且都有一个"app=my-blog"的标签。

{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "my-blog"
    },
    "spec": {
        "selector": {
            "app": "my-blog"
        },
        "ports": [
            {
                "protocol": "TCP",
                "port": 8080,
                "targetPort": 80
            }
        ]
    }
}

这个定义会创建一个新的Service对象,名字为”my-blog”,它指向所有带有”app=my-blog”标签的Pod上面的80端口。这个Service同时也会被分配一个IP地址(有时被称作”cluster ip”),它会被服务的代理所使用(见下面)。这个Service的选择器,会不断的对Pod进行筛选,并将结果POST到名字同样为“my-blog”的Endpoints对象。

注意一个Service能将一个来源的端口映射到任意的targetPort。默认情况下,targetPort会被设置成与port字段一样的值。可能更有意思的地方在于,targetPort可以是一个字符串,能引用一个后端Pod中定义的端口名。实际指派给该名称的端口号在每一个Pod中可能会不同。这为部署和更新你的Service提供了很大的灵活性。例如,你可以在你的后端的下一个版本中更改开放的端口,而无需导致客户出现故障。

Kubernetes的Service支持TCP和UDP协议。默认是TCP。

Endpoint

说到service不得不提 endpoint。在 Service 创建的同时,还生成了一个 Endpoints。 该 Endpoints 与 Service 同名,它所暴露的地址信息正是对应 Pod 的地址。由此猜测是 Endpoints 维护了 Service 与 Pod 的映射关系。
为了验证我们的猜测,我们手动删除 Endpoints,发现之前能成功访问到 Pod 的 VIP,现在已经已经访问不到了。

ServiceController 主要处理的还是与 LoadBalancer 相关的逻辑,但是 EndpointController 的作用就没有这么简单了,我们在使用 Kubernetes 时虽然很少会直接与 Endpoint 资源打交道,但是它却是 Kubernetes 中非常重要的组成部分。

EndpointController 本身并没有通过 Informer 监听 Endpoint 资源的变动,但是它却同时订阅了 Service 和 Pod 资源的增删事件,它会根据 Service 对象规格中的选择器 Selector 获取集群中存在的所有 Pod,并将 Service 和 Pod 上的端口进行映射生成一个 EndpointPort 结构体,对于每一个 Pod 都会生成一个新的 EndpointSubset,其中包含了 Pod 的 IP 地址和端口和 Service 的规格中指定的输入端口和目标端口,在最后 EndpointSubset 的数据会被重新打包并通过客户端创建一个新的 Endpoint 资源。所以,除了 Service 的变动会触发 Endpoint 的改变之外,Pod 对象的增删也会触发。

在我的理解,service就是将不断变动的pod做了一个固定的端口映射(别管pod怎么变化,怎么漂移,这是我的事),你只需要处理service暴露出来的固定端口即可。到底是谁来处理service暴露出来的端口呢?那当然是代理了。

kube-proxy

我们知道 Service 的代理是由 kube-proxy 实现的。而它的代理模式(Proxy mode)主要有两种:userspace 与 iptables。自 K8S v1.2 开始,默认的代理模式就是 iptables(/ipvs),并且它的性能也是要高于 userspace 的

userspace是在用户空间,通过kuber-proxy实现LB的代理服务。这个是kube-proxy的最初的版本,较为稳定,但是效率也自然不太高。
iptables的方式。是纯采用iptables来实现LB。是目前一般kube默认的方式。
ipvs:这种模式从Kubernetes 1.11进入GA,并在Kubernetes 1.12成为kube-proxy的默认代理模式。ipvs模式也是基于netfilter,对比iptables模式在大规模Kubernetes集群有更好的扩展性和性能,支持更加复杂的负载均衡算法(如:最小负载、最少连接、加权等),支持Server的健康检查和连接重试等功能。ipvs依赖于iptables,使用iptables进行包过滤、SNAT、masquared。ipvs将使用ipset需要被DROP或MASQUARED的源地址或目标地址,这样就能保证iptables规则数量的固定,我们不需要关心集群中有多少个Service了。

我们现在要做的呢,是将 VIP 请求给转发到对应的 Pod 上。而实现此的正是 iptables(/ipvs)。
举个例子,现在有podA,podB,podC和serviceAB。serviceAB是podA,podB的服务抽象(service)。 那么kube-proxy的作用就是可以将pod(不管是podA,podB或者podC)向serviceAB的请求,进行转发到service所代表的一个具体pod(podA或者podB)上。 请求的分配方法一般分配是采用轮询方法进行分配。

那有了kube-proxy,service是如何暴露的呢?
service的ServiceTypes能让你指定你想要哪一种服务。默认的和基础的是ClusterIP,这会开放一个服务可以在集群内部进行连接。NodePort 和LoadBalancer是两种会将服务开放给外部网络的类型。

apiVersion: v1
kind: Service
metadata:
  # Service 实例名称
  name: my-blog
spec:
  ports:
    - protocol: TCP
      # Service 端口地址
      port: 8080
      # Pod 端口地址
      targetPort: 80
  selector:
    # 匹配符合标签条件的 Pod
    app: my-blog
  type: NodePort    <- Service Type

ServiceType字段的合法值是:
ClusterIP: 仅仅使用一个集群内部的IP地址 - 这是默认值,在上面已经讨论过。选择这个值意味着你只想这个服务在集群内部才可以被访问到。
NodePort: 在集群内部IP的基础上,在集群的每一个节点的端口上开放这个服务。你可以在任意<NodeIP>:NodePort地址上访问到这个服务。

如果定义为NodePort,Kubernetes master 将从给定的配置范围内(默认:30000-32767)分配端口,每个 Node 将从该端口代理到 Service。该端口将通过 Service 的 spec.ports[*].nodePort 字段被指定。那么我们就可以使用任意节点IP:NodePort来访问到my-blog容器的80端口

LoadBalancer: 在使用一个集群内部IP地址和在NodePort上开放一个服务之外,向云提供商申请一个负载均衡器,会让流量转发到这个在每个节点上以<NodeIP>:NodePort的形式开放的服务上。
在使用一个集群内部IP地址和在NodePort上开放一个Service的基础上,还可以向云提供者申请一个负载均衡器,将流量转发到已经以NodePort形式开发的Service上。

将上述service文件保存并执行命令

$ kubectl create -f service.yaml

service/my-blog created

表明我们成功创建了一个使用NodePort方式暴露的service
下面我们来验证一下

clipboard.png

通过上第一行命令,我们可以看到刚刚创建的service已经以NodePort的方式对外暴露(第一个红框),暴露的端口号是32388,通过另外两行命令我们的项目my-blog部署在哪一台物理机节点上(对应EXTERNAL-IP就是物理机的真实IP),这里害怕遇到调皮的同学,所以给它加了个衣服:P

尝试通过IP+Port的方式请求一下

clipboard.png

Amazing! 通了!终于不是独乐乐了~

不过... 以用户的习惯,通过IP+端口号来访问的姿势可能无法接受
可能我们还需要再搞个域名,让用户可以直接通过域名来访问我们的博客
这时候有些机灵鬼就跳出来了,这还不简单!上面再搭个nginx不就行了...
确实是这样...但是如果我再多部署一个博客呢?(那就再加一条nginx解析记录呗...我们不是一直这样用的吗???
那...我们还搞个毛k8s
k8s可不是为了让你部署一两个服务而诞生的...它是为了成千上万个服务~(那你为什么在教我们用k8s部署博客???我:你今天有点话多
如果每一个服务都要手动去修改nginx解析记录...你就不怕什么时候你手多抖两下吗?

Ingress

总结一下上面service直接暴露服务的一些缺点

  • 一个app 需要占用一个主机端口
  • 端口缺乏管理
  • L4转发, 无法根据http header 和 path 进行路由转发

而Ingress就是为了解决上面的问题而诞生的
Ingress 的实现分为两个部分 Ingress Controller 和 Ingress .

  • Ingress Controller 是流量的入口,是一个实体软件, 一般是Nginx 和 Haproxy 。
  • Ingress 描述具体的路由规则。

Ingress Controller 会监听 api server上的 /ingresses 资源 并实时生效。
Ingerss 描述了一个或者多个 域名的路由规则,以 ingress 资源的形式存在。

简单说: Ingress 描述路由规则, Ingress Controller 负责动态实现规则。

下面我们来动手实现一下ingress,通过动手来学习
首先需要在我们的集群中安装ingress-nginx-controller
官方文档:https://kubernetes.github.io/...

// 登录集群,在集群中执行以下命令安装 ingress-nginx
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml

验证:

$ kubectl get pod -n ingress-nginx
NAME                                        READY   STATUS    RESTARTS   AGE
nginx-ingress-controller-66f7bd6b88-fwshc   1/1     Running   0          8s

说明安装成功
但是官方提供的这个yaml并没有指定nginx-ingress-controller自己本身的暴露方式
下面我们来手动设置以hostNetWork的方式暴露服务

$ kubectl edit deploy nginx-ingress-controller -n ingress-nginx
// 修改hostNetWork为true
spec.template.spec.hostNetWork: ture

deployment.extensions/nginx-ingress-controller edited  

修改成功!下面我们来创建一个ingress资源

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-blog
  namespace: test
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"  // 这个参数标识 不强制要求使用HTTPS协议进行通信,文章结尾会讲到https的支持
spec:
  rules:
  - host: blog.holdno.com
    http:
      paths:
      - path: /
        backend:
          serviceName: my-blog
          servicePort: 8080

通过上面的yaml文件我们可以很清晰的看出,我们想要对用户暴露的域名是 blog.holdno.com,解析规则是根路径"/",对应的service为my-blog,转发到service的8080端口(前面我们service暴露的是8080端口到POD的80端口)

$ kubectl create -f ingress.yaml

ingress.extensions/my-blog created

$ kubectl get ingress -n test
NAME      HOSTS             ADDRESS   PORTS   AGE
my-blog   blog.holdno.com             80      10s

ingress我们就部署成功了

clipboard.png

通过上面两条命令找到nginx-ingress-controller所在的节点的真实IP(在部署ingress时最好是指定某几台性能较好的节点,通过nodeSelector进行调度)
通过域名解析平台将我们的域名blog.holdno.com解析到节点IP上

curl blog.holdno.com 
// 下面会出现完美的一幕,大家亲手实践一下吧

到此,我们便成功的建立了从服务到用户之间的桥梁!之后无论POD怎么漂移,对于上层的ingress都是无谓的
虽然桥通了,但是感觉部署一个服务的过程很是心累...
后面的文章,我们将通过k8s提供的client-go来为大家梳理出一套自动化的部署流程,让我们的博客可以一键上云!

Ingress https

既然支持http了 自然少不了https的需求
那么我们就动手一步一步实现 ingress https的支持吧

首先需要了解的是 k8s内置的 secret资源,用来保存证书信息
我们通过阿里云购买一个免费的证书,并下载到机器上。(假设保存路径为 /ssl/blog.crt 和 /ssl/blog.key)

$ kubectl create secret tls my-secret --cert /ssl/blog.crt --key ./ssl/blog.key --namespace test // 一定要注意,secret是区分namespace的,不同namespace下的ingress资源和secret资源互相不可见  

$ secret/blog created

修改我们的ingress配置

$ kubectl edit ingress my-blog -n test  

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx","nginx.ingress.kubernetes.io/ssl-redirect":"false"},"name":"my-blog","namespace":"test"},"spec":{"rules":[{"host":"blog.holdno.com","http":{"paths":[{"backend":{"serviceName":"my-blog","servicePort":777},"path":"/"}]}}]}}
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/ssl-redirect: "false" // 如果要强制使用https协议,需要将这个参数改为 true
  creationTimestamp: "2019-09-17T12:20:01Z"
  generation: 3
  name: my-blog
  namespace: test
  resourceVersion: "1107154526"
  selfLink: /apis/extensions/v1beta1/namespaces/test/ingresses/my-blog
  uid: 78bac7dc-d945-11e9-b8cf-9a285cd2373e
spec:
  rules:
  - host: blog.holdno.com
    http:
      paths:
      - backend:
          serviceName: my-blog
          servicePort: 8080
        path: /
  tls:  <- 关键配置
  - hosts:
    - blog.holdno.com # 证书对应的host
    secretName: blog  # 对应的证书  也就是我们上一步创建的secret
status:
  loadBalancer: {}  

ingress.extensions/my-blog edited

接下里我们就可以在浏览器中访问 https://blog.holdno.com 来验证一下。

查看原文

赞 0 收藏 0 评论 0

holdno 发布了文章 · 2019-09-18

2. 说好不哭,HelloWorld

通过上一篇文章的摸爬滚打,一个“完整”的kubernetes集群已经出现在我们眼前。通过目光可以看出我们现在是又兴奋又焦虑...
k8s有是有了,它怎么用???
它怎么用???
它怎么用???
(我也是一脸懵逼,但我不敢说啊

(硬着头皮
那我们先来摸索一下,k8s界的helloworld:echoserver

Deployment

名如其人,这就是k8s的部署模块
首先我们先创建一个名为test的namespace来关联我们的测试行为

namespace是什么:Namespace(命名空间)是kubernetes系统中的另一个重要的概念,通过将系统内部的对象“分配”到不同的Namespace中,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。

我们可以通过创建一个Kubernetes Deployment对象来运行一个应用, 可以在一个YAML文件中描述Deployment. 例如, 下面这个YAML文件描述了一个k8s界的helloworld

apiVersion: extensions/v1beta1
kind: Deployment // 定义对象类型
metadata:
  name: my-blog   // 定义服务名称
  namespace: test  // 定义namespace
spec:
  replicas: 1  // 定义该服务在集群中初始部署的个数,这也是未来能够支撑我们发布并发出轨文章的重要参数之一
  selector:
    matchLabels:
      app: my-blog
  template:
    metadata:
      labels:
        app: my-blog
    spec:
      containers:  // 定义POD中容器的具体信息
      - name: my-blog
        image: googlecontainer/echoserver:1.10
        ports:
        - containerPort: 8080 // 定义容器对外暴露的端口
        env:  // 设置容器的环境变量
          - name: NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: spec.nodeName
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
          - name: POD_IP
            valueFrom:
              fieldRef:
                fieldPath: status.podIP

通过内容可以看出,这是一个被命名为my-blog的项目,监听容器的8080端口,容器镜像是一个docker hub上公共的echoserver镜像
我们将上述内容保存在一个echoserver.yaml文件中
通过执行命令 kubectl create -f echoserver.yaml 可以看到控制台输出 deployment.extensions/my-blog created 则表示我们的demo已经被部署在了k8s集群中

验证:

$ kubectl get deploy -n test
NAME      READY   UP-TO-DATE   AVAILABLE   AGE
my-blog   1/1     1            1           3h22m

验证无误!
接下来我们就试着请求一下看看,是否可以真的工作

// 由于本专栏的重点是从0-1部署个人博客,所以k8s相关的操作就简单解释一下:P
// 首先我们先获取由上一步deploy出来的pod信息
$ kubectl get pod -n test -o wide
NAME                       READY   STATUS    RESTARTS   AGE     IP           NODE       NOMINATED NODE   READINESS GATES
my-blog-7bcb7cdb76-jkcsh   1/1     Running   0          3h25m   172.16.0.5   10.0.0.9   <none>           <none>
什么是POD:在Kubernetes中,最小的管理元素不是一个个独立的容器,而是Pod,Pod是最小的,管理,创建,计划的最小单元。
一个Pod(就像一群鲸鱼,或者一个豌豆夹)相当于一个共享context的配置组,在同一个context下,应用可能还会有独立的cgroup隔离机制,一个Pod是一个容器环境下的“逻辑主机”,它可能包含一个或者多个紧密相连的应用,这些应用可能是在同一个物理主机或虚拟机上。
Pod 的context可以理解成多个linux命名空间的联合
PID 命名空间(同一个Pod中应用可以看到其它进程)
网络 命名空间(同一个Pod的中的应用对相同的IP地址和端口有权限)
IPC 命名空间(同一个Pod中的应用可以通过VPC或者POSIX进行通信)
UTS 命名空间(同一个Pod中的应用共享一个主机名称)
同一个Pod中的应用可以共享磁盘,磁盘是Pod级的,应用可以通过文件系统调用,额外的,一个Pod可能会定义顶级的cgroup隔离,这样的话绑定到任何一个应用(好吧,这句是在没怎么看懂,就是说Pod,应用,隔离)

找到POD IP后我们尝试对它发起一个GET请求,

$ curl 172.16.0.5:8080 // 上述配置文件显示,my-blog这个项目的容器对外监听的是8080端口

// 成功得到请求结果
Hostname: my-blog-7bcb7cdb76-jkcsh

Pod Information:
    node name:    10.0.0.9
    pod name:    my-blog-7bcb7cdb76-jkcsh
    pod namespace:    test
    pod IP:    172.16.0.5

Server values:
    server_version=nginx: 1.13.3 - lua: 10008

Request Information:
    client_address=172.16.0.1
    method=GET
    real path=/
    query=
    request_version=1.1
    request_scheme=http
    request_uri=http://172.16.0.5:8080/

Request Headers:
    accept=*/*
    host=172.16.0.5:8080
    user-agent=curl/7.47.0

Request Body:
    -no body in request-

感动,居然成功了。想想我们把它换成真正的博客页面,那是多么美好的一件事情!
不对...
这个POD的IP是怎么回事?172.16.0.5这个IP我好想没见过啊,我的机器不是这个IP啊!
前面有讲到,Kubernetes的最小部署单元是Pod。k8s利用Flannel(或其他网络插件)作为不同HOST之间容器互通技术时,由Flannel和etcd维护了一张节点间的路由表。Flannel的设计目的就是为集群中的所有节点重新规划IP地址的使用规则,从而使得不同节点上的容器能够获得“同属一个内网”且”不重复的”IP地址,并让属于不同节点上的容器能够直接通过内网IP通信。

每个Pod启动时,会自动创建一个镜像为gcr.io/google_containers/pause:0.8.0的容器,容器内部与外部的通信经由此容器代理,该容器的IP也可以称为Pod IP。

原来啊,这些IP是Flannel创建出来的一个个内网IP地址,顾名思义,这个IP只有在集群网络内才可以通信。那作为博客站点,光能自己看有啥意思啊!唉,感觉忙活了半天坑了自己。

好在今天还是有点收获,实现了k8s的helloworld。但是只能内网通信的POD........可咋整???

查看原文

赞 0 收藏 0 评论 0

holdno 发布了文章 · 2019-09-17

0. 万事开头难,需求分析

每个程序员都希望能够一个属于自己的个人站点。那么就跟着作者,通过本专栏一起来搭建一个个人站点吧!

想到搭建个人站点,最基础的也就是准备好我们的运行环境(不应该是开发环境吗???,开发环境因人而异,语言千万种选择自己喜欢的就好啦)。
吸取日常的生活经验,我们领会到凡事三思而后行。
首先我们来分析下需求,个人站点、个人博客,为了能够让读者随时随地的浏览,那肯定离不开一个稳定的运行环境。
谈到稳定的环境,不得不皱起了三层厚的眉头。从我们本地开发,到测试环境,到生产环境...凭一己之力实在是不能保证运行环境的一致性。那我们肯定是要祭出解决环境一致性问题的神器 Docker。
想到Docker,额头总算是平整了一些。但是想到日后万一我的博客发出了世纪新闻(并发出轨?),那这弱小的docker那肯定是扛不住了呀。怎么办呢???(上k8s啊
啥?
k8s???
哦对,借助k8s的自动扩容无限伸缩搞定它!那...那我们用k8s来部署我们的个人站点吧!如果不想接数据库还能用用configmap来写写文章你说是不是。简直就是为博客而生啊!
那就让我们开始动手吧~

(不负责声明,本专栏所有内容是作者将自己的学习过程分享给大家,如有误导请指正

查看原文

赞 0 收藏 0 评论 0

holdno 发布了文章 · 2019-09-16

1. 通过RKE快速部署kubernetes集群

图片描述

前期准备

RKE:Ranchar Kubernetes Engine
https://github.com/rancher/rke
由于RKE是由golang编写,所以直接下载对应系统的二进制文件即可
下载地址: https://github.com/rancher/rk...

1.建议使用Ubuntu 16.04.3 LTS版本;如果使用CentOS 7,建议7.3以上版本
2.各主机的hostname主机名必须不同!
3.hosts文件设置:/etc/hosts要配置正确,一定要有127.0.0.1 localhost 这一项;hosts文件中包含所有主机节点的IP和名称列表。

Docker

通过Rancher提供的脚本进行安装(以下是最新支持的docker版本)

Docker版本安装脚本
18.09.2curl https://releases.rancher.com/... | sh
18.06.2curl https://releases.rancher.com/... | sh
17.03.2curl https://releases.rancher.com/... | sh

设置docker用户组

RKE通过SSH tunnel进行安装部署,需要事先建立RKE到各节点的SSH免密登录。如果集群中有3个节点,需要在RKE机器上执行1遍秘钥生成命令ssh-keygen,并将生成侧公钥通过分发命令:ssh-copy-id {user}@{ip}。

在各个节点上创建ssh用户,并将其添加至docker组中:

useradd dockeruser
usermod -aG docker dockeruser 
注意:重启系统以后才能生效,只重启Docker服务是不行的!重启后,docker_user用户也可以直接使用docker run命令。

关闭Selinux

Ubuntu 16.04默认未安装,无需设置。

1)CentOS7下可修改配置文件

vi /etc/sysconfig/selinux

2)设置 SELINUX=disabled,重启后永久关闭。

设置IPV4转发

必须开启!Ubuntu 16.04下默认已启用,无需设置。

1)CentOS7 下可编辑配置文件:

vi /etc/sysctl.conf

2)设置:

net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1

3)执行如下命令生效:

sudo sysctl -p

设置防火墙策略

开放集群主机节点之间6443、2379、2380端口,如果是刚开始试用,可以先关闭防火墙;

systemctl stop firewalld

Ubuntu默认未启用UFW防火墙,无需设置。也可手工关闭:sudo ufw disable

禁用Swap

一定要禁用swap,否则kubelet组件无法运行。
1)永久禁用swap
可以直接修改

vi /etc/fstab

文件,注释掉swap项。
2)临时禁用

swapoff -a

启用cgroup

修改配置文件/etc/default/grub,启用cgroup内存限额功能,配置两个参数:

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"
GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"

注意:要执行sudo update-grub 更新grub,然后重启系统后生效。
CentOS下执行:

grub2-mkconfig -o /boot/grub2/grub.cfg

设置SSH

1)在rke所在主机上创建密钥:

ssh-keygen

2)将所生成的密钥的公钥分发到各个节点:

ssh-copy-id dockeruser@xxx.xxx.xx.xx
...

编写 cluster.yml

可以输入命令rke_drawin-amd64 config(我本机是mac,所以使用drawin-amd64版本),跟着引导完成基础配置
配置完成后将会在当前目录下出现一个 cluster.yml
下面是该配置文件的内容(这里我配置的是包含两个节点的k8s集群,其中一个master,rke很容易支持HA部署,仅仅需要在配置文件中对多个节点指定role.controlplane即可)

nodes:
- address: xxx.xxx.xxx.xxx
  port: "22"
  internal_address: ""
  role:
  - controlplane
  - worker
  - etcd
  hostname_override: master
  user: dockeruser
  docker_socket: /var/run/docker.sock
  labels: {}
- address: xxx.xxx.xxx.xxx
  port: "22"
  internal_address: ""
  role:
  - worker
  hostname_override: node-1
  user: dockeruser
  docker_socket: /var/run/docker.sock
  ssh_key: ""
  ssh_key_path: ""
  ssh_cert: ""
  ssh_cert_path: ""
  labels: {}
services:
  etcd:
    image: ""
    extra_args: {}
    extra_binds: []
    extra_env: []
    external_urls: []
    ca_cert: ""
    cert: ""
    key: ""
    path: ""
    snapshot: null
    retention: ""
    creation: ""
    backup_config: null
  kube-api:
    image: ""
    extra_args: {}
    extra_binds: []
    extra_env: []
    service_cluster_ip_range: 10.43.0.0/16
    service_node_port_range: ""
    pod_security_policy: false
    always_pull_images: false
  kube-controller:
    image: ""
    extra_args: {}
    extra_binds: []
    extra_env: []
    cluster_cidr: 10.42.0.0/16
    service_cluster_ip_range: 10.43.0.0/16
  scheduler:
    image: ""
    extra_args: {}
    extra_binds: []
    extra_env: []
  kubelet:
    image: ""
    extra_args: {}
    extra_binds: []
    extra_env: []
    cluster_domain: cluster.local
    infra_container_image: ""
    cluster_dns_server: 10.43.0.10
    fail_swap_on: false
  kubeproxy:
    image: ""
    extra_args: {}
    extra_binds: []
    extra_env: []
network:
  plugin: flannel
  options: {}
authentication:
  strategy: x509
  sans: []
  webhook: null
addons: ""
addons_include: []
system_images:
  etcd: rancher/coreos-etcd:v3.3.10-rancher1
  alpine: rancher/rke-tools:v0.1.42
  nginx_proxy: rancher/rke-tools:v0.1.42
  cert_downloader: rancher/rke-tools:v0.1.42
  kubernetes_services_sidecar: rancher/rke-tools:v0.1.42
  kubedns: rancher/k8s-dns-kube-dns:1.15.0
  dnsmasq: rancher/k8s-dns-dnsmasq-nanny:1.15.0
  kubedns_sidecar: rancher/k8s-dns-sidecar:1.15.0
  kubedns_autoscaler: rancher/cluster-proportional-autoscaler:1.3.0
  coredns: rancher/coredns-coredns:1.3.1
  coredns_autoscaler: rancher/cluster-proportional-autoscaler:1.3.0
  kubernetes: rancher/hyperkube:v1.14.6-rancher1
  flannel: rancher/coreos-flannel:v0.10.0-rancher1
  flannel_cni: rancher/flannel-cni:v0.3.0-rancher1
  calico_node: rancher/calico-node:v3.4.0
  calico_cni: rancher/calico-cni:v3.4.0
  calico_controllers: ""
  calico_ctl: rancher/calico-ctl:v2.0.0
  canal_node: rancher/calico-node:v3.4.0
  canal_cni: rancher/calico-cni:v3.4.0
  canal_flannel: rancher/coreos-flannel:v0.10.0
  weave_node: weaveworks/weave-kube:2.5.0
  weave_cni: weaveworks/weave-npc:2.5.0
  pod_infra_container: rancher/pause:3.1
  ingress: rancher/nginx-ingress-controller:0.21.0-rancher3
  ingress_backend: rancher/nginx-ingress-controller-defaultbackend:1.5-rancher1
metrics_server: rancher/metrics-server:v0.3.1
ssh_key_path: ~/.ssh/id_rsa
ssh_cert_path: ""
ssh_agent_auth: true
authorization:
  mode: rbac
  options: {}
ignore_docker_version: false
kubernetes_version: ""
private_registries: []
ingress:
  provider: ""
  options: {}
  node_selector: {}
  extra_args: {}
cluster_name: ""
cloud_provider:
  name: ""
prefix_path: ""
addon_job_timeout: 0
bastion_host:
  address: ""
  port: ""
  user: ""
  ssh_key: ""
  ssh_key_path: ""
  ssh_cert: ""
  ssh_cert_path: ""
monitoring:
  provider: ""
  options: {}
restore:
  restore: false
  snapshot_name: ""
dns: null

安装Kubernetes

rke_drawin-amd64 up

可以看到脚本开始输入一些日志信息
最终出现

Finished building Kubernetes cluster successfully

则表示集群安装成功。

验证

集群安装成功后,RKE会在当前目录创建一个kube_config_cluster.yml文件,这个文件就是kubeconfig文件

默认情况下,kube配置文件被称为.kube_config_cluster.yml。将这个文件复制到你的本地~/.kube/config,就可以在本地使用kubectl了。

需要注意的是,部署的本地kube配置名称是和集群配置文件相关的。例如,如果您使用名为mycluster.yml的配置文件,则本地kube配置将被命名为.kube_config_mycluster.yml。

export KUBECONFIG=./kube_config_cluster.yml
kubectl get node
NAME     STATUS   ROLES                      AGE     VERSION
master   Ready    controlplane,etcd,worker   6m27s   v1.14.6
node-1   Ready    worker                     6m5s    v1.14.6

看到节点信息表明安装成功

添加或删除节点

RKE支持为角色为worker和controlplane的主机添加或删除节点。
1)添加节点:
要添加其他节点,只需要更新具有其他节点的集群配置文件,并使用相同的文件运行集群配置即可。
2)删除节点:
要删除节点,只需从集群配置文件中的节点列表中删除它们,然后重新运行rke up命令。

高可用

RKE工具是满足高可用的。您可以在集群配置文件中指定多个控制面板主机,RKE将在其上部署主控组件。
默认情况下,kubelets被配置为连接到nginx-proxy服务的地址——127.0.0.1:6443,该代理会向所有主节点发送请求。
要启动HA集群,只需使用controlplane角色指定多个主机,然后正常启动集群即可。

删除集群

RKE支持rke remove命令。该命令执行以下操作:
连接到每个主机并删除部署在其上的Kubernetes服务。
从服务所在的目录中清除每个主机:

  • /etc/kubernetes/ssl
  • /var/lib/etcd
  • /etc/cni
  • /opt/cni

请注意,这个命令是不可逆的,它将彻底摧毁Kubernetes集群。

安装中可能遇到的问题

ssh: handshake failed: ssh: unable to authenticate, attempted methods [publickey none], no supported methods remain

请检查配置文件中配置的用户是否可以使用指定的私钥登录机器

查看原文

赞 3 收藏 0 评论 2

holdno 关注了用户 · 2019-09-04

wuYin @wuyin

关注 444

认证与成就

  • 获得 198 次点赞
  • 获得 137 枚徽章 获得 5 枚金徽章, 获得 44 枚银徽章, 获得 88 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • vue-uploader

    vue结合plupload异步图片上传组件(有七牛JSSDK版本)

  • holdno/alidayu

    golang实现最新(2017-05-25)版阿里大于SDK

注册于 2017-03-27
个人主页被 1.8k 人浏览