sudo 出现unable to resolve host

每次执行sudo 就出现这个警告讯息:

sudo: unable to resolve host xxx

虽然有提示,其实并不影响执行。最近在运行某个脚本也出现了这个提示。这就不得了了。

解决办法也简单,在/etc/hosts中设定xxx(hostname) 指向 127.0.0.1 的 IP 即可:

127.0.0.1       localhost xxx  #要保证这个名字与 /etc/hostname中的主机名一致

# 或改成下面这两行
127.0.0.1       localhost
127.0.0.1       xxx

yarn 容器化及基本操作

容器化早已深入我服务器的方方面面。最近用到yarn,下面记录下前端框架 yarn 的镜像构建的过程,和一些常用的 yarn 的命令。

Dockerfile

FROM node:10

# Debian packages
RUN apt-get -y update && \
  apt-get install -y --no-install-recommends \
    fakeroot \
    lintian \
    rpm \
    ruby \
    ruby-dev \
  && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/*

# Ruby packages
RUN gem install fpm

WORKDIR /var/www/html

我把它编译成了镜像kelvinblood/yarn

docker-compose

version:  '3.2'

  yarn:
    image: kelvinblood/yarn
    restart: always
    container_name: yarn
    stdin_open: true
    tty: true
    volumes:
      - ./:/var/www/html:rw
      - /usr/local/share/.cache/yarn:/usr/local/share/.cache/yarn
#    entrypoint:  [ "/bin/bash"]
    entrypoint:
      - npm
      - run
      - watch
      - --
      - --watch-poll

中间那个entrypoint是为了测试用的,下面是生产用的。

常用操作

yarn config set registry 'https://registry.npm.taobao.org'
SASS_BINARY_SITE=http://npm.taobao.org/mirrors/node-sass yarn
yarn add china-area-data
yarn add sweetalert
npm run watch-poll

参考资料


理解 K8S 的设计精髓之 list-watch - koala bear

博主是蘑菇街的员工,写了不少kubernetes的文章。这篇的原文链接:http://wsfdl.com/kubernetes/2019/01/10/list_watch_in_k8s.html

至今清楚的记得,当了解到 K8S 组件之间仅采用 HTTP 协议通信,没有依赖中间件时,我非常好奇它是如何做到的。

在 K8S 内部通信中,肯定要保证消息的实时性。之前以为方式有两种:客户端(kubelet, scheduler, controller-manager 等)轮询 apiserver,或者 apiserver 通知客户端。如果采用轮询,势必会大大增加 apiserver 的压力,同时实时性很低。如果 apiserver 主动发 HTTP 请求,又如何保证消息的可靠性,以及大量端口占用问题?

当阅读完 list-watch 源码后,先是所有的疑惑云开雾散,进而为 K8S 的设计理念所折服。List-watch 是 K8S 统一的异步消息处理机制,保证了消息的实时性,可靠性,顺序性,性能等等,为声明式风格的 API 奠定了良好的基础,它是优雅的通信方式,是 K8S 架构的精髓。

List-Watch 是什么

Etcd 存储集群的数据信息,apiserver 作为统一入口,任何对数据的操作都必须经过 apiserver。客户端(kubelet/scheduler/ontroller-manager)通过 list-watch 监听 apiserver 中资源(pod/rs/rc 等等)的 create, update 和 delete 事件,并针对事件类型调用相应的事件处理函数。

那么 list-watch 具体是什么呢,顾名思义,list-watch 有两部分组成,分别是 list 和 watch。list 非常好理解,就是调用资源的 list API 罗列资源,基于 HTTP 短链接实现;watch 则是调用资源的 watch API 监听资源变更事件,基于 HTTP 长链接实现,也是本文重点分析的对象。以 pod 资源为例,它的 list 和 watch API 分别为:

List API,返回值为 PodList,即一组 pod。

GET /api/v1/pods

Watch API,往往带上 watch=true,表示采用 HTTP 长连接持续监听 pod 相关事件,每当有事件来临,返回一个 WatchEvent

GET /api/v1/watch/pods

K8S 的 informer 模块封装 list-watch API,用户只需要指定资源,编写事件处理函数,AddFunc, UpdateFunc 和 DeleteFunc 等。如下图所示,informer 首先通过 list API 罗列资源,然后调用 watch API 监听资源的变更事件,并将结果放入到一个 FIFO 队列,队列的另一头有协程从中取出事件,并调用对应的注册函数处理事件。Informer 还维护了一个只读的 Map Store 缓存,主要为了提升查询的效率,降低 apiserver 的负载。

list watch

Watch 是如何实现的

List 的实现容易理解,那么 Watch 是如何实现的呢?Watch 是如何通过 HTTP 长链接接收 apiserver 发来的资源变更事件呢?

秘诀就是 Chunked transfer encoding(分块传输编码),它首次出现在 HTTP/1.1 。正如维基百科所说:

HTTP 分块传输编码允许服务器为动态生成的内容维持 HTTP 持久链接。通常,持久链接需要服务器在开始发送消息体前发送Content-Length消息头字段,但是对于动态生成的内容来说,在内容创建完之前是不可知的。使用分块传输编码,数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。

当客户端调用 watch API 时,apiserver 在 response 的 HTTP Header 中设置 Transfer-Encoding 的值为 chunked,表示采用分块传输编码,客户端收到该信息后,便和服务端该链接,并等待下一个数据块,即资源的事件信息。例如:

$ curl -i http://{kube-api-server-ip}:8080/api/v1/watch/pods?watch=yes
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 02 Jan 2019 20:22:59 GMT
Transfer-Encoding: chunked

{"type":"ADDED", "object":{"kind":"Pod","apiVersion":"v1",...}}
{"type":"ADDED", "object":{"kind":"Pod","apiVersion":"v1",...}}
{"type":"MODIFIED", "object":{"kind":"Pod","apiVersion":"v1",...}}
...

谈谈 List-Watch 的设计理念

当设计优秀的一个异步消息的系统时,对消息机制有至少如下四点要求:

  • 消息可靠性
  • 消息实时性
  • 消息顺序性
  • 高性能

首先消息必须是可靠的,list 和 watch 一起保证了消息的可靠性,避免因消息丢失而造成状态不一致场景。具体而言,list API 可以查询当前的资源及其对应的状态(即期望的状态),客户端通过拿期望的状态和实际的状态进行对比,纠正状态不一致的资源。Watch API 和 apiserver 保持一个长链接,接收资源的状态变更事件并做相应处理。如果仅调用 watch API,若某个时间点连接中断,就有可能导致消息丢失,所以需要通过 list API 解决消息丢失的问题。从另一个角度出发,我们可以认为 list API 获取全量数据,watch API 获取增量数据。虽然仅仅通过轮询 list API,也能达到同步资源状态的效果,但是存在开销大,实时性不足的问题。

消息必须是实时的,list-watch 机制下,每当 apiserver 的资源产生状态变更事件,都会将事件及时的推送给客户端,从而保证了消息的实时性。

消息的顺序性也是非常重要的,在并发的场景下,客户端在短时间内可能会收到同一个资源的多个事件,对于关注最终一致性的 K8S 来说,它需要知道哪个是最近发生的事件,并保证资源的最终状态如同最近事件所表述的状态一样。K8S 在每个资源的事件中都带一个 resourceVersion 的标签,这个标签是递增的数字,所以当客户端并发处理同一个资源的事件时,它就可以对比 resourceVersion 来保证最终的状态和最新的事件所期望的状态保持一致。

List-watch 还具有高性能的特点,虽然仅通过周期性调用 list API 也能达到资源最终一致性的效果,但是周期性频繁的轮询大大的增大了开销,增加 apiserver 的压力。而 watch 作为异步消息通知机制,复用一条长链接,保证实时性的同时也保证了性能。

最后

List-Watch 基于 HTTP 协议,是 K8S 重要的异步消息通知机制。它通过 list 获取全量数据,通过 watch API 监听增量数据,保证消息可靠性,实时性,性能和顺序性。而消息的实时性,可靠性和顺序性又是实现声明式设计的良好前提。它简洁优雅,功能强大,是 K8S 的精髓之一。本人读后,叹为观止。


kubernetes 调度实践之节点标签/污点、应用容忍/nodeSelector/配额/反亲和/lifecycle

平时常给节点添加标签/污染,还有给应用添加容忍、配额和反亲和,这篇文章记录一些常用的命令,方便查阅。

本文中的特性基于kubernetes 1.10.

一、节点标签

节点标签的用处主要是便于标识每台机器由哪个应用团队使用,也可用于调度方面。

添加:

kubectl label nodes <node-name> app=wechat.kelu.org

删除:

命令行最后指定 Label 的 key 名并与一个减号相连:

kubectl label nodes <node-name> app- 

修改:

加上–overwrite参数:

kubectl label nodes <node-name> app=blog.kelu.org --overwrite

二、污点和容忍

污点和容忍常搭配用来做容器调度的,可以将某几台机器指定用于特定的应用。需要注意的是,默认情况下污点和容忍对daemonset不生效。

为节点添加污点:

kubectl taint node <node-name> app=blog.kelu.org:NoSchedule

为 deployment 添加容忍,与 yaml 文件的containers平级

"tolerations": [
          {
            "key": "app",
            "operator": "Equal",
            "value": "blog.kelu.org",
            "effect": "NoSchedule"
          }
        ]

其中effect的值可以为NoSchedule ,PreferNoSchedule ,NoExecute

查看节点的描述:

kubectl describe nodes <node-name>

删除污点:

kubectl taint nodes <node-name> app:NoSchedule-

三、选择节点

pod选择制定节点运行

        "nodeSelector": {
          "app": "app=blog.kelu.org"
        },

四、资源限制

可以通过 ResourceQuotaLimitRange 对namespace里的pod进行限制。 ResourceQuota 是 namespace 中所有 Pod 占用资源的 request 和 limit, LimitRange 是 namespace 中单个 Pod 的默认资源 request 和 limit。

同时,也可以在 deployment 中对单个 pod 进行限制。

ResourceQuota限制资源占用

apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-resources
  namespace: spark-cluster
spec:
  hard:
    pods: "20"
    requests.cpu: "20"
    requests.memory: 100Gi
    limits.cpu: "40"
    limits.memory: 200Gi

ResourceQuota限制资源数量

apiVersion: v1
kind: ResourceQuota
metadata:
  name: object-counts
  namespace: spark-cluster
spec:
  hard:
    configmaps: "10"
    persistentvolumeclaims: "4"
    replicationcontrollers: "20"
    secrets: "10"
    services: "10"
    services.loadbalancers: "2"

LimitRange 配置单个 Pod 的 CPU和内存

apiVersion: v1
kind: LimitRange
metadata:
  namespace: blog
  name: mem-cpu-res-limit
spec:
  limits:
  - default:
      memory: 5G
      cpu: 5
    defaultRequest:
      memory: 1Gi
      cpu: 1
    max:
      memory: 1024Mi
      cpu: 1
    min:
      memory: 128Mi
      cpu: 0.5
    type: Container    
  • default:即该命名空间配置resourceQuota时,创建容器的默认限额上限
  • defaultRequest:即该命名空间配置resourceQuota时,创建容器的默认请求上限
  • max:即该命名空间下创建容器的资源最大值
  • min:即该命名空间下创建容器的资源最小值

在 deployment 中对 pod 限制

apiVersion: extensions/v1beta1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: xxx
        image: xxx
        resources:
          limits:
            cpu: 4
            memory: 5Gi
          requests:
            cpu: 100m
            memory: 1Gi

五、亲和性调度

Affinity 是“亲和性”, Anti-Affinity,翻译成“互斥”或“反亲和”。

我们常用反亲和来让同一类Pod平均分布在不同的机器上。

目前有两种主要的 node affinity:

  • requiredDuringSchedulingIgnoredDuringExecution :pod 必须部署到满足条件的节点上,如果没有满足条件的节点,就不断重试;
  • preferredDuringSchedulingIgnoredDuringExecution :优先部署在满足条件的节点上,如果没有满足条件的节点,就忽略这些条件,按照正常逻辑部署。

requiredDuringSchedulingIgnoredDuringExecution

     affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                io.service: blog
            "topologyKey": "kubernetes.io/hostname"

preferredDuringSchedulingIgnoredDuringExecution

      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: io.service
                  operator: In
                  values:
                  - blog
              topologyKey: kubernetes.io/hostname

更详细的说明请参考官方文档:

六、lifecycle

在容器调度时,如果容器正在处理请求,粗暴的杀死会造成应用的不稳定。为了应对这种情况,让Pod更优雅地退出/调度,kubernetes 提供了lifecycle 的特性。目前 lifecycle 可以在容器生命周期定义了两个钩子:

  • PostStart, 在容器创建后运行(不能保证会在entrypoint前运行)
  • PreStop 在容器终止前调用

我们可以通过 PreStop 钩子,通过延迟容器终止,让容器运行完可能的请求再退出:

apiVersion: extensions/v1beta1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: xxx
        image: xxx
	    lifecycle:
          preStop:
            exec:
              command: ["sleep","5s"]

延迟退出的时间需要更多的应用测试来把握。

参考资料