C0reFast记事本

to inspire confidence in somebody.

最近公司有个Ceph集群出了点问题,于是也参与了修复的过程,过程中最让人头疼的就是一堆不明所以的状态了,所以看了看文档,也找了一些参考,
整理了一下Ceph PG的一些状态以及相关的概念说明,做了一个中英文的对照版本:

Placement Group States(PG状态)

当检查一个集群的状态时(执行ceph -w或者ceph -s),Ceph会汇报当前PG的状态,每个PG会有一个或多个状态,最优的PG状态是active + clean
下面是所有PG状态的具体解释:

creating

Ceph is still creating the placement group.
Ceph 仍在创建PG。

activating

The placement group is peered but not yet active.
PG已经互联,但是还没有active。

active

Ceph will process requests to the placement group.
Ceph 可处理到此PG的请求。

clean

Ceph replicated all objects in the placement group the correct
number of times.
PG内所有的对象都被正确的复制了对应的份数。

down

A replica with necessary data is down, so the placement group is
offline.
一个包含必备数据的副本离线,所以PG也离线了。

scrubbing

Ceph is checking the placement group metadata for inconsistencies.
Ceph 正在检查PG metadata的一致性。

deep

Ceph is checking the placement group data against stored checksums.
Ceph 正在检查PG数据和checksums的一致性。

degraded

Ceph has not replicated some objects in the placement group the
correct number of times yet.
PG中的一些对象还没有被复制到规定的份数。

inconsistent

Ceph detects inconsistencies in the one or more replicas of an
object in the placement group (e.g. objects are the wrong size,
objects are missing from one replica *after* recovery finished,
etc.).
Ceph检测到PG中对象的一份或多份数据不一致(比如对象大学不一直,或者恢复成功后对象依然没有等)

peering

The placement group is undergoing the peering process
PG正在互联过程中。
阅读全文 »

本文是Kubernetes DNS-Based Service Discovery的翻译,也就是Kubernetes DNS specification的翻译,目前最新版本号是1.0.1。

0 - 关于此文档

本文档是Kubernetes基于DNS的服务发现的规范说明,尽管在Kubernetes中还有其他方式的服务发现协议和机制,但是DNS仍然是最常见而且是最推荐使用的扩展。实际的DNS服务并不一定由由默认的Kube-DNS提供。 本文档旨在为所有实现之间的通用性提供相应的基准。

阅读全文 »

在Docker环境中经常会遇到时区相关的问题,所以顺便也看了一下Linux系统下时间相关的一下配置和概念:

硬件时钟

硬件时钟也叫RTC(Real Time Clock)或者CMOS时钟,这个是保存在BIOS中的,仅能保存:年、月、日、时、分、秒这些时间数值,无法保存当前时区以及是否使用夏令时调节。

系统时钟

系统时钟也叫软件时钟,在系统时钟里是有时区等概念的,在Linux内核里,是保存为自 UTC 时间 1970 年1月1日经过的秒数。系统启动时会读取硬件时钟,并根据/etc/adjtime的设置计算当前的时钟。系统启动之后,系统时钟与硬件时钟独立运行,Linux 通过时钟中断计数维护系统时钟。

/etc/localtime

这个文件一般情况下是一个软链接,链接到/usr/share/zoneinfo/目录下的一个对应时区的二进制文件,比如设置Asia/Shanghai的时区,则/etc/localtime -> /usr/share/zoneinfo/Asia/Shanghai,调用date等工具获取时间时会考虑这个配置。
建议并强烈建议将这个文件设置为软链接,很多人会直接拷贝文件,其实是不推荐的。所有的Linux系统都会依赖这个文件。

/etc/timezone

这个文件一般会记录时区的直接文字表示,或者是一个时间偏移(很少见),比如如果设置时区为Asia/Shanghai,则这个文件的内容就会是Asia/Shanghai。这个文件并不是在所有Linux中都存在,比如在我的ArchLinux中就没有这个文件。这个文件一般也仅仅是一个简单的表示。

最后我们基本可以得到下面的结论:

  1. BIOS时间即硬件时间没有时区
  2. Linux在启动时会根据/etc/adjtime的设置和当前的硬件时间计算出当前的UTC时间,并将其和1970 年1月1日的秒差记录在内核中,由时钟中断继续维护。
  3. date等工具获得的时间是根据Linux内核中保存的时间和/etc/localtime的配置计算得来的。

其他相关问题,比如设置时间以及和Windows时间同步等可以参考下面的ArchLinux Wiki链接

参考:

  1. https://wiki.archlinux.org/index.php/Time
  2. https://unix.stackexchange.com/questions/384971/whats-the-difference-between-localtime-and-timezone-files

最近的一个项目需要用到Kubernetes的CronJob,主要用来定时执行一个备份任务,刚开始使用的时候发现没有按照预期的情况运行,所以决定看看CronJob Controller的代码,看看他是怎么实现对应的功能的,正好发现网上也没有其他人写过关于CronJob Controller代码的解析(可能是太简单了不用写吧)。所以也就正好记录一下。

CronJob Controller的代码在kubernetes/pkg/controller/cronjob路径下,主要的逻辑实现在这个目录的cronjob_controller.go,这里分析的是v1.10.2版本的代码,可以直接链接到Github查看。

阅读全文 »

之前一直听说Designing Data-Intensive Applications (DDIA) 这本书是神书,也决定读一下,顺便做些笔记,也算巩固一下学到的东西吧。

书的第一部分,主要关注于一些基础的知识,第一章的标题Reliable, Scalable, and Maintainable Applications就讲了三个当前应用的最主要特点:可靠性,可扩展性和可维护性。

可靠性

首先是Reliability也就是可靠性,主要包含下面的几个预期:

  • 应用可以按用户所期待的功能正常运行
  • 可以容忍用户犯的错误或者不正确的使用方式
  • 在对应的系统容量下性能能满足正常的使用要求
  • 能拒绝未授权或者滥用的情况

如果能满足上面的需求,可以说一个软件是可靠的,但是并不是所有的东西都能满足预期,当一些意料之外的东西发生了,称之为faults,系统正确应对这些faults的能力则称作fault-tolerant or resilient(即容错能力或者弹性),虽然容错能力很重要,但也不是意味着可以实现一个能容忍任何错误的系统(比如地球爆炸了)。
需要注意的是,faultfailure是不一样的,前者一般指的是系统某个部分没有按照设计正常工作,而后者一般就意味着整个系统都无法正常提供服务了。当然,我们没有办法去降低fault发生的概率,特别是降低到0,能做的,就是当fault发生时,系统不会因为这些faults变成failure状态,这也是一个容错系统的设计目标。
针对一个系统,我们可以人为的提升faults的发生概率,来验证系统的可靠性,比如kill某个进程,或者断开网络等等。一般情况下,比较严重的bug都是因为对错误的处理不完善导致的。
当然尽管我们可以通过设计容忍很多错误,但是预防错误的发生,远远比发生后再去修复来的好,毕竟很多错误是没办法被修复的,比如数据库被黑客入侵,这个操作无法被修复到原始的样子(数据已经泄漏)。
常见的错误主要有三个:

阅读全文 »

转自The complete guide to Go net/http timeouts

服务端超时

对于http.Server服务端有两个超时可以设置:ReadTimeoutWriteTimeout

srv := &http.Server{
    ReadTimeout: 5 * time.Second,
    WriteTimeout: 10 * time.Second,
}
log.Println(srv.ListenAndServe())

各自的作用时间见图:

Server timeout

需要注意的是WriteTimeout被设置了两次,一次是在读取Http头过程中,另一次是在读取Http头结束后。

客户端超时

对于http.Client客户端,相对要复杂一点,一般的初始化代码如下:

c := &http.Client{
    Transport: &http.Transport{
        Dial: (&net.Dialer{
                Timeout:   30 * time.Second,
                KeepAlive: 30 * time.Second,
        }).Dial,
        TLSHandshakeTimeout:   10 * time.Second,
        ResponseHeaderTimeout: 10 * time.Second,
        ExpectContinueTimeout: 1 * time.Second,
    }
}

这些Timeout各自的作用时间见:

Client timeout

在CNI所以默认提供的Plugin中,bridge应该算是最简单的插件了,针对IPAM Plugin,最简单的应该是host-local,这两个插件也是Kubernetes网络kubenet需要的两个插件。所以这里看一下这两个插件的代码。

所有官方维护的代码,都开源在containernetworking/plugins项目中了。

其中bridge的代码在plugins/main/bridge目录,最重要的是cmdAddcmdDel两个函数,对应CNI SPEC中的ADD和DEL两个主要操作。主要来看一下cmdAdd的实现,精简(删除一些错误处理)后的代码如下:

func cmdAdd(args *skel.CmdArgs) error {
	n, cniVersion, err := loadNetConf(args.StdinData)

	if n.IsDefaultGW {
		n.IsGW = true
	}

	br, brInterface, err := setupBridge(n)
	if err != nil {
		return err
	}

	netns, err := ns.GetNS(args.Netns)
	defer netns.Close()

	hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode)

	r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
	if err != nil {
		return err
	}

	result, err := current.NewResultFromResult(r)

	if len(result.IPs) == 0 {
		return errors.New("IPAM plugin returned missing IP config")
	}

	result.Interfaces = []*current.Interface{brInterface, hostInterface, containerInterface}

	gwsV4, gwsV6, err := calcGateways(result, n)
	if err != nil {
		return err
	}

	if err := netns.Do(func(_ ns.NetNS) error {
		contVeth, err := net.InterfaceByName(args.IfName)

		for _, ipc := range result.IPs {
			if ipc.Version == "6" && (n.HairpinMode || n.PromiscMode) {
				if err := disableIPV6DAD(args.IfName); err != nil {
					return err
				}
				break
			}
		}

		if err := ipam.ConfigureIface(args.IfName, result); err != nil {
			return err
		}

		for _, ipc := range result.IPs {
			if ipc.Version == "4" {
				_ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth)
			}
		}
		return nil
	}); err != nil {
		return err
	}

	if n.IsGW {
		var firstV4Addr net.IP
		for _, gws := range []*gwInfo{gwsV4, gwsV6} {
			for _, gw := range gws.gws {
				if gw.IP.To4() != nil && firstV4Addr == nil {
					firstV4Addr = gw.IP
				}

				err = ensureBridgeAddr(br, gws.family, &gw, n.ForceAddress)
				if err != nil {
					return fmt.Errorf("failed to set bridge addr: %v", err)
				}
			}

			if gws.gws != nil {
				if err = enableIPForward(gws.family); err != nil {
					return fmt.Errorf("failed to enable forwarding: %v", err)
				}
			}
		}
	}

	if n.IPMasq {
		chain := utils.FormatChainName(n.Name, args.ContainerID)
		comment := utils.FormatComment(n.Name, args.ContainerID)
		for _, ipc := range result.IPs {
			if err = ip.SetupIPMasq(ip.Network(&ipc.Address), chain, comment); err != nil {
				return err
			}
		}
	}

	br, err = bridgeByName(n.BrName)

	brInterface.Mac = br.Attrs().HardwareAddr.String()

	result.DNS = n.DNS

	return types.PrintResult(result, cniVersion)
}
阅读全文 »

本文主要介绍一下容器网络接口(CNI) 的SPEC,主要参考SPEC v0.3.1,以及目前最新的SPEC,目前新SPEC针对v0.3.1版本改动不是很大,特别是相关接口输入输出方面,因此可以看作是一样的。

总览

所有的CNI Plugin,都必须实现为可以被容器管理系统(如rtk、Kubernetes等)调用的可执行文件。
CNI插件负责将网络接口插入容器网络命名空间(例如veth pair的其中一端),并在主机上进行任何必要的改变(例如将veth pair的另一端连接到网桥)。然后应该将IP分配给接口,并通过调用适当的IPAM插件将与“IP地址管理”部分一致的IP地址分配给该网络接口,并设置好对应的路由。

参数

所有的CNI Plugin必须实现以下操作:

  • 添加一个容器到网络

    • Parameters:
      • Version. The version of CNI spec that the caller is using (container management system or the invoking plugin).
      • Container ID. A unique plaintext identifier for a container, allocated by the runtime. Must not be empty.
      • Network namespace path. This represents the path to the network namespace to be added, i.e. /proc/[pid]/ns/net or a bind-mount/link to it.
      • Network configuration. This is a JSON document describing a network to which a container can be joined. The schema is described below.
      • Extra arguments. This provides an alternative mechanism to allow simple configuration of CNI plugins on a per-container basis.
      • Name of the interface inside the container. This is the name that should be assigned to the interface created inside the container (network namespace); consequently it must comply with the standard Linux restrictions on interface names.
    • Result:
      • Interfaces list. Depending on the plugin, this can include the sandbox (eg, container or hypervisor) interface name and/or the host interface name, the hardware addresses of each interface, and details about the sandbox (if any) the interface is in.
      • IP configuration assigned to each interface. The IPv4 and/or IPv6 addresses, gateways, and routes assigned to sandbox and/or host interfaces.
      • DNS information. Dictionary that includes DNS information for nameservers, domain, search domains and options.
  • 从网络中删除一个容器

    • Parameters:
      • Version. The version of CNI spec that the caller is using (container management system or the invoking plugin).
      • Container ID, as defined above.
      • Network namespace path, as defined above.
      • Network configuration, as defined above.
      • Extra arguments, as defined above.
      • Name of the interface inside the container, as defined above.
    • All parameters should be the same as those passed to the corresponding add operation.
    • A delete operation should release all resources held by the supplied containerid in the configured network.
  • 报告插件支持的CNI版本

    • Parameters: NONE.

    • Result: information about the CNI spec versions supported by the plugin

      {
        "cniVersion": "0.3.1", // the version of the CNI spec in use for this output
        "supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1" ] // the list of CNI spec versions that this plugin supports
      }
阅读全文 »

默认情况下,在使用rpmbuild打包时,会对安装的所有文件进行strip操作,去除文件的一些调试信息,并将这些调试信息放到debuginfo包中,但在很多时候,我们并不需要rpmbuild帮我们执行strip,也不需要生成debuginfo包,所以我们可以修改一下spec文件,关闭这些选项。

针对文件的strip操作是在__os_install_post这个宏中定义的,我们可以运行一下rpmbuild --showrc看一下原始的__os_install_post做了哪些操作:

...
-14: __os_install_post
    /usr/lib/rpm/redhat/brp-compress
    %{!?__debug_package:/usr/lib/rpm/redhat/brp-strip %{__strip}}
    /usr/lib/rpm/redhat/brp-strip-static-archive %{__strip}
    /usr/lib/rpm/redhat/brp-strip-comment-note %{__strip} %{__objdump}
    /usr/lib/rpm/brp-python-bytecompile
    /usr/lib/rpm/redhat/brp-python-hardlink
    %{!?__jar_repack:/usr/lib/rpm/redhat/brp-java-repack-jars}
...

可以看到在打包时会对文件进行一系列操作,比如压缩,strip,编译Python脚本等,所以,我们只需要在spec文件中,加上%define __os_install_post %{nil},将__os_install_post设置为空,这样在打包的时候,就不会执行上面的这些操作了,也就不会对文件进行strip操作了。同样的,如果不需要生成debuginfo包,只需要再加上%define debug_package %{nil}就可以了。

这个问题是很久之前解决的,现在想起来,还是把之前的问题解决过程总结一下。
问题的起因是内部的一个Socket代理,用户对独享数据库的所有请求都需要经过这个Socket代理,某天一个用户反馈,切换到独享数据库之后,页面响应变得异常的慢,大概从1s左右直接到了60s左右,明显是有问题的,首先让用户开了xhprof看了一下,发现用户一个页面牵涉到了超过1000次SQL查询,这1000多次查询占据了绝大部分的时间,因为仅仅切换了数据库,所以问题的原因肯定还是数据库相关。
这个场景还是稍微有点特殊,一个页面里有超过1000次SQL查询的设计也不算合理,所以,我们就编写了测试用例,在PHP中,查询数据库1000次,测试直接连接数据库,和通过Socket代理连接数据库的情况:最后发现直连的速度非常快,但是过代理则慢的不可接受了,很明显是代理的问题。
于是我们尝试在代理机器上抓包分析一下:

tcpdump

其中10.67.15.102是我们Web运行环境的机器IP,10.67.15.212是Socket代理所在的机器10.13.144.139是数据库所在的机器,从id为22953的数据包开始,到22957,是一个SQL查询从Web运行环境到数据库的整个过程:

id为22953:运行环境102发送select语句到Socket代理212。数据包长度为296byte 时间:23.877515
id为22954:Socket代理212发送了一部分select语句128byte到数据库139。       时间:23.877611
id为22955:Socket代理212回运行环境103的ack。                             时间:23.917294
id为22956:数据库139回Socket代理212的ack。                               时间:23.918398
id为22957:Socket代理212发送剩余部分select语句168byte到数据库139。       时间:23.918415
阅读全文 »
0%