技术

如何使用RedisTemplate访问Redis数据结构 MySQL重要知识点 OAuth2认证授授权流程 分布式锁 服务调用 MQ的介绍 SpringCloud 使用链 Eureka 的点对点通信 介绍Eureka RabbitMQ与其它MQ的对比 Springboot 启动过程分析 Springboot 入门 Linux内存管理 自定义CNI IPAM 扩展Kubernetes 副本一致性 spring redis 源码分析 kafka实践 spring kafka 源码分析 Linux进程调度 让kafka支持优先级队列 Codis源码分析 Redis源码分析 C语言学习 《趣谈Linux操作系统》笔记 Kubernetes安全机制 jvm crash分析 Prometheus 学习 Kubernetes监控 Kubernetes 控制器模型 容器日志采集 容器狂占cpu怎么办? 容器狂打日志怎么办? Kubernetes资源调度-scheduler 时序性数据库介绍及对比 influxdb入门 maven的基本概念 《Apache Kafka源码分析》——server Kubernetes objects之编排对象 源码分析体会 自动化mock AIOps说的啥 从DevOps中挖掘docker的价值 《数据结构与算法之美》——算法新解 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes整体结构 ansible学习 Kubernetes源码分析——从kubectl开始 jib源码分析之Step实现 kubernetes实践 线程排队 jib源码分析之细节 从一个签名框架看待机制和策略 跨主机容器通信 jib源码分析及应用 docker环境下的持续构建 docker环境下的持续发布 一个容器多个进程 kubernetes yaml配置 marathon-client 源码分析 《持续交付36讲》笔记 程序猿应该知道的 mybatis学习 无锁数据结构和算法 《Container-Networking-Docker-Kubernetes》笔记 活用linux 命令 为什么很多业务程序猿觉得数据结构和算法没用? 串一串一致性协议 当我在说PaaS时,我在说什么 《数据结构与算法之美》——数据结构笔记 swagger PouchContainer技术分享体会 harbor学习 用groovy 来动态化你的代码 《深入剖析kubernetes》笔记 精简代码的利器——lombok 学习 java 语言的动态性 rxjava3——背压 rxjava2——线程切换 spring cloud 初识 JVM4——《深入拆解java 虚拟机》笔记 《how tomcat works》笔记 commons-pipeline 源码分析 hystrix 学习 rxjava1——概念 Redis 学习 TIDB 学习 分布式计算系统的那些套路 Storm 学习 AQS3——论文学习 Unsafe Spark Stream 学习 linux 文件系统 mysql 批量操作优化 《自己动手写docker》笔记 java8 实践 中本聪比特币白皮书 细读 区块链泛谈 比特币 大杂烩 总纲——如何学习分布式系统 forkjoin 泛谈 hbase 泛谈 看不见摸不着的cdn是啥 《jdk8 in action》笔记 程序猿视角看网络 calico 问题排查 bgp初识 mesos 的一些tips mesos 集成 calico calico AQS2——粗略的代码分析 我们能用反射做什么 web 跨域问题 《clean code》笔记 compensable-transaction 源码分析 硬件对软件设计的影响 elasticsearch 初步认识 mockito简介及源码分析 线上用docker要解决的问题 《Apache Kafka源码分析》——Producer与Consumer 停止容器 dns隐藏的一个坑 《mysql技术内幕》笔记2 《mysql技术内幕》笔记1 log4j学习 为什么netty比较难懂? 回溯法 apollo client源码分析及看待面向对象设计 java系并发模型的发展 从一个marathon的问题开始的 docker 环境(主要运行java项目)常见问题 Scala的一些梗 OpenTSDB 入门 spring事务小结 事务一致性 javascript应用在哪里 netty中的future和promise 《netty in action》读书笔记 netty对http2协议的解析 ssl证书是什么东西 一些tricky的code http那些事 苹果APNs推送框架pushy apple 推送那些事儿 编写java框架的几大利器 JVM3——java内存模型 java concurrent 工具类 java exception java io涉及到的一些linux知识 network channel network byte buffer 测试环境docker化实践 通用transport层框架pigeon netty(七)netty在框架中的使用套路 Nginx简单使用 《Linux内核设计的艺术》小结 从Go并发编程模型想到的 mesos深入 Macvlan Linux网络源代码学习2 《docker源码分析》小结 对web系统的一些理解 docker中涉及到的一些linux知识 hystrix学习 Linux网络源代码学习 Docker网络五,docker网络的回顾 zookeeper三重奏 数据库的一些知识 Spark 泛谈 commons-chain netty(六)netty回顾 Thrift基本原理与实践(三) Thrift基本原理与实践(二) Thrift基本原理与实践(一) Future 回调 Docker0.1.0源码分析 基于spring boot和Docker搭建微服务 通过Docker Plugin来扩展Docker Engine java gc Docker网络四,基于Centos搭建Docker跨主机网络 google guava的一些理解 Jedis源码分析 Redis概述 Docker回顾 深度学习是个什么鬼 Docker网络三,基于OVS实现Docker跨主机网络 Linux网络命令操作 JTA与TCC 换个角度看待设计模式 Scala初识 netty(四)netty对http协议的实现(废弃) netty(三)netty框架泛谈 向Hadoop学习NIO的使用 以新的角度看数据结构 AQS1——并发相关的硬件与内核支持 使用Ubuntu要做的一些环境准备 Docker网络二,libnetwork systemd 简介 那些有用的sql语句 异构数据库表在线同步 spring aop 实现原理简述——背景知识 quartz 源码分析 基于docker搭建测试环境(二) spring aop 实现原理简述 我们编程的那些潜意识 自己动手写spring(八) 支持AOP 自己动手写spring(七) 类结构设计调整 分析log日志 一次代码调试的过程 自己动手写spring(六) 支持FactoryBean 自己动手写spring(九) 总结 自己动手写spring(五) bean的生命周期管理 自己动手写spring(四) 整合xml与注解方式 自己动手写spring(三) 支持注解方式 自己动手写spring(二) 创建一个bean工厂 自己动手写spring(一) 使用digester varnish 简单使用 docker volume 关于docker image的那点事儿 基于docker搭建测试环境 分布式配置系统 JVM2——JVM和传统OS对比 git spring rmi和thrift maven/ant/gradle使用 再看tcp mesos简介 缓存系统——具体组件 缓存系统 java nio的多线程扩展 多线程设计模式/《Concurrency Models》笔记 回头看Spring IOC IntelliJ IDEA使用 Java泛型 vagrant 使用 Go 常用的一些库 Netty(一)初步了解 java mina Golang开发环境搭建(Windows下) java nio入门 ibatis自动生成类和文件 Python初学 Goroutine 调度模型猜想 一些编程相关的名词 虚拟网络 《程序员的自我修养》小结 VPN(Virtual Private Network) Hadoop安装与调试 Kubernetes持久化存储 Kubernetes 其它特性 访问Kubernetes上的服务 Kubernetes副本管理 Kubernetes pod 组件 使用etcd + confd + nginx做动态负载均衡 nginx安装与简单使用 在CoreOS集群上搭建Kubernetes 如何通过fleet unit files 来构建灵活的服务 CoreOS 安装 定制自己的boot2docker.iso CoreOS 使用 Go初学 JVM1——jvm小结 硬币和扑克牌问题 LRU实现 virtualbox 使用 os->c->java 多线程 容器类概述 zabbix 使用 zabbix 安装 Linux中的一些点 关于集群监控 ThreadLocal小结 我对Hadoop的认识 haproxy安装 docker快速入门

标签


如何通过fleet unit files 来构建灵活的服务

2015年01月11日

简介

原文地址:How to Create Flexible Services for a CoreOS Cluster with Fleet Unit Files,有删减。

CoreOS利用一系列的工具,大大简化了集群和Docker服务的管理。其中,Etcd将独立的节点连接起来,并提供一个存储全局数据的地方。大部分实际的服务管理和管理员任务则有fleet来负责。

上一个guide中,我们过了一遍fleetctl的基本使用:操纵服务和集群中的节点。在那个guide中,我们简单的介绍了fleet unit文件,fleet使用它来定义服务。还创建一个简单的unit 文件,展示了如何使用fleetctl提供一个工作的service。

在本guide中,我们深度学习fleet unit文件,了解一些使你的service更加健壮的技巧。

准备

为了完成本教程,我们假设你已经根据我们以往的教程创建了一个CoreOS集群,假定集群包含以下主机:

  • coreos-1
  • coreos-2
  • coreos-3

尽管本教程的大部分内容是关于unit文件的创建,但在描述unit文件中指令(属性)的作用时会用到这些机器。我们同样假设你阅读了如何使用fleetctl,你已经可以使用fleetctl将unit文件部署到集群中。

当你已经做好这些准备后,请继续阅读本教程。

unit文件的section和类型

因为fleet的服务管理方面主要依赖于集群节点本地的systemd,所以systemd unit file基本就是 fleet unit file。

fleet unit file 有许多类型,service是最常用的一种。这里列出一些 systemd unit file支持的类型,每个类型(文件)以”文件名.类型名”标识(命名)。比如example.service

  • service
  • socket
  • device
  • mount
  • automount
  • timer
  • path

尽管这些类型都是有效的,但service类型用的最多。在本guide中,我们也只讨论service类型的配置。

unit 文件是简单的文本文件,以”文件名.类型名(上面提到的)”命名。在文件内部,它们以section 组织,对于fleet,大部分unit 文件是以下格式:

[Unit]
generic_unit_directive_1
generic_unit_directive_2

[Service]
service_specific_directive_1
service_specific_directive_2
service_specific_directive_3

[X-Fleet]
fleet_specific_directive

每个小节的头和其他部分都是大小写敏感的。unit小节用来定义一个unit文件的通用信息,在unit小节定义的信息对所有类型(比如service)都通用。

Service小节用来设置一些只有Service类型文件才用到的指令,上面提到的每一个类型(但不是全部)(比如service和socket)都有相应的节来定义本类型的特定信息。

fleet根据X-Fleet小节来判定如何调度unit文件。在该小节中,你可以设定一些条件来将unit文件部署到某台主机上。

Building the Main Service

在本节,我们的unit文件将和 basic guide on running services on CoreOS中提到的有所不同。这个文件叫apache.1.service,具体内容如下:

[Unit]
Description=Apache web server service

# Requirements
Requires=etcd.service
Requires=docker.service
Requires=apache-discovery.1.service

# Dependency ordering
After=etcd.service
After=docker.service
Before=apache-discovery.1.service

[Service]
# Let processes take awhile to start up (for first run Docker containers)
TimeoutStartSec=0

# Change killmode from "control-group" to "none" to let Docker remove
# work correctly.
KillMode=none

# Get CoreOS environmental variables
EnvironmentFile=/etc/environment

# Pre-start and Start
## Directives with "=-" are allowed to fail without consequence
ExecStartPre=-/usr/bin/docker kill apache
ExecStartPre=-/usr/bin/docker rm apache
ExecStartPre=/usr/bin/docker pull username/apache
ExecStart=/usr/bin/docker run --name apache -p ${COREOS_PUBLIC_IPV4}:80:80 \
username/apache /usr/sbin/apache2ctl -D FOREGROUND

# Stop
ExecStop=/usr/bin/docker stop apache

[X-Fleet]
# Don't schedule on the same machine as other Apache instances
X-Conflicts=apache.*.service

我们先说unit section,在本seciton,描述了unit 的基本信息和依赖情况。服务启动前有一系列的requirements,并且本例中使用的是hard requirements。如果我们想让fleet在启动本服务时启动其它服务,并且如果其他服务启动失败不影响本服务的启动,我们可以使用Wants来代替requirements。

接下来,我们明确的列出了requirements 启动的顺序。在服务运行前,确定其依赖的服务是可用的非常重要。我们也是通过这种方式来启动一个该服务的所有的从服务(Sidekick Service,后文会提到)。

在service 小节中,我们关闭了服务启动间隔时间。因为当服务开始在一个节点上运行时,相应镜像需要先从doker registry中pull下来,pull的时间会被计入startup timeout。这个值默认是90秒,通常是足够的。但对于比较复杂的容器来说,可能需要更长时间。

我们把killmode 设置为none,这是因为正常的kill 模式有时会导致容器移除命令执行失败(尤其是执行docker --rm containerID/containername时),这会导致下次启动时出问题。

我们把environment 文件也“拉进水”,这样我们就可以访问COREOS_PUBLIC_IPV4。如果服务创建时私有网络可用,COREOS_PRIVATE_IPV4是可配的,我们便可以使用每个主机自己的信息配置其container。

ExecStartPre 用来清除上次运行的残留,确保本次的执行环境是干净的。对于头两个ExecStartPre 使用“=-”,来告诉systemd:即使这两个命令执行失败了,也继续执行接下来的命令。这样,docker 将尝试kill 并且 移除先前的容器,没有发现容器也没有关系。最后一个ExecStartPre用来确保将要运行的container是最新的。

X-fleet section包含一个简单的条件,强制fleet将service调度某个没有运行Apache 服务的机器上。这样可以很容易的提高服务的可靠性,因为它们运行在不同的机器上(一个机器挂了,还有另外的机器在运行该服务)。

Building the Sidekick Announce Service

现在当我们构建主unit文件时候,已经有了一些不错的想法。接下来,我们看一下传统的从service。这个从 service和主 serivce有一定联系,被用来向etcd注册服务。

这个文件,就像它在主unit文件中被引用的那样,叫做apache-discovery.1.service,内容如下:

[Unit]
Description=Apache web server etcd registration

# Requirements
Requires=etcd.service
Requires=apache.1.service

# Dependency ordering and binding
After=etcd.service
After=apache.1.service
BindsTo=apache.1.service

[Service]

# Get CoreOS environmental variables
EnvironmentFile=/etc/environment

# Start
## Test whether service is accessible and then register useful information
ExecStart=/bin/bash -c '\
  while true; do \
    curl -f ${COREOS_PUBLIC_IPV4}:80; \
    if [ $? -eq 0 ]; then \
      etcdctl set /services/apache/${COREOS_PUBLIC_IPV4} \'{"host": "%H", "ipv4_addr": ${COREOS_PUBLIC_IPV4}, "port": 80}\' --ttl 30; \
    else \
      etcdctl rm /services/apache/${COREOS_PUBLIC_IPV4}; \
    fi; \
    sleep 20; \
  done'

# Stop
ExecStop=/usr/bin/etcdctl rm /services/apache/${COREOS_PUBLIC_IPV4}

[X-Fleet]
# Schedule on the same machine as the associated Apache service
X-ConditionMachineOf=apache.1.service

和讲述主service一样,先从文件的unit节开始。依赖关系和启动顺序就不谈了。

第一个新的指令(参数)是BindsTo=,这个指令意味着 当fleet start、stop和restart apache.1.service时,对apache-discovery.1.service也做同样操作。这意味着,我们使用fleet 只操作一个主服务,即同时操作了主服务与与之”BindsTo”的服务。并且,这个机制是单向的,fleet操作apache-discovery.1.serviceapache.1.service没有影响。

对于service节,我们应用了/etc/environment文件,因为我们需要它包含的环境变量。ExecStart=在此处是一个bash脚本,它企图使用暴漏的ip和端口访问主服务。

如果连接成功,使用etcdctl设置etcd中key为/services/apache/{COREOS_PUBLIC_IPV4}的值为一个json串:{"host": "%H", "ipv4_addr": ${COREOS_PUBLIC_IPV4}, "port": 80}。这个key值的有效期为30s,以此来确保一旦这个节点down掉,etcd中不会残留这个key值的信息。如果连接失败,则立即从etcd移除key值,因为主服务已经失效了。

循环有20s的休息时间,每隔20秒(在30s内etcd中key失效之前)重新检查一下主服务是否有效并重置key。这将更新ttl时间,确保在下一个30秒内key值是有效的。

本例中,stop指令仅仅是将key从etcd中移除,当stop主服务时,因为BIndsTo=,本服务也将被执行,从而从etcd移除注册信息。

对于X-fleet小节,我们需要确保该unit和主unit运行在一台机器上。结合BindsTo=指令,这样做可以将主节点信息汇报到远程主机上。

Fleet-Specific Considerations

X-Fleet小节用来描述如何调度unit文件,具有以下指令(属性):

  • X-ConditionMachineID: 它可以指定一个特定的machine来加载unit。

  • X-ConditionMachineOf: 它用来设定将unit部署到运行某个特定unit的machine上。

  • X-Conflicts: 这个和上一个参数的作用恰恰相反, 它指定了本unit不想和哪些unit运行在同一台机器上。

  • X-ConditionMachineMetadata: 它基于machine的metadata来确定调度策略。

  • Global: 是一个布尔值,用来确定你是否想把unit部署到集群的每一台machine上。

这些额外的directives(属性)让管理员灵活的在集群上部署服务,在unit被提交到machinie的systemd实例之前,(directives)指定的策略将会被执行。

还有一个问题,什么时候使用fleet来部署相关的unit。除了X-fleet 小节外,fleetctl工具不检查units之间的依赖是否满足。因此在使用fleet部署相关的unit时,会有一些有趣的问题。

这意味着,尽管fleetctl工具会执行必要的步骤直到unit变成期望的状态:提交,加载,执行unit中的命令。但不会解决unit之间的依赖问题。

所以,如果你同时提交了主从两个unit:A和B,但A和B还没有加载。执行fleetctl start A.service将加载并执行A.service unit。然而,因为B.service unit还没有加载,并且因为fleetctl 不会检查unit之间的依赖,A.service将会执行失败。因为systemd会检查依赖是否满足,一旦主机的systemd开始执行A.service,却没有找到B.service,systemd便会终止A.service的执行。

为了避免这种伙伴unit执行失败的情况,你可以手动同时启动两个unitfleet start A.service B.service ,不要依赖BindsTo=执令。

另一种方法是当运行主unit的时候,确保从unit至少已经被加载。被加载意味着一台machine已经被选中,并且unit file已被提交到systemd实例,此时满足了依赖条件,BindsTo参数也能够正确执行。

fleetctl load A.service B.service
fleetctl start A.service

如果fleetctl执行多个unit时失败,请记起这一点。

Instances and Templates

unit template是fleet一个非常有用的概念。

unit templates依赖systemd一个叫做”instance”的特性。systemd运行时通过计算template unit文件实例化的unit file。template文件和普通unit文件大致相同,只有一点改进。但如果正确使用,威力无穷。

你可以在文件名中加入”@”来标识一个template 文件,比如一个传统的service文件名:unit.service,对应template文件则可以叫做unit@.service。 当template文件被实例化的时候,在”@”和”.service”之间将有一个用户指定的标识符:unit@instance_id.service。在template文件内部,可以用%p来指代文件名(即unit),类似的,%i可以指代标识符(即instance_id)。

Main Unit File as a Template

我们可以创建一个template文件:apache@.service。

[Unit]
Description=Apache web server service on port %i

# Requirements
Requires=etcd.service
Requires=docker.service
Requires=apache-discovery@%i.service

# Dependency ordering
After=etcd.service
After=docker.service
Before=apache-discovery@%i.service

[Service]
# Let processes take awhile to start up (for first run Docker containers)
TimeoutStartSec=0

# Change killmode from "control-group" to "none" to let Docker remove
# work correctly.
KillMode=none

# Get CoreOS environmental variables
EnvironmentFile=/etc/environment

# Pre-start and Start
## Directives with "=-" are allowed to fail without consequence
ExecStartPre=-/usr/bin/docker kill apache.%i
ExecStartPre=-/usr/bin/docker rm apache.%i
ExecStartPre=/usr/bin/docker pull username/apache
ExecStart=/usr/bin/docker run --name apache.%i -p ${COREOS_PUBLIC_IPV4}:%i:80 \
username/apache /usr/sbin/apache2ctl -D FOREGROUND

# Stop
ExecStop=/usr/bin/docker stop apache.%i

[X-Fleet]
# Don't schedule on the same machine as other Apache instances
X-Conflicts=apache@*.service

就像你看到的,我们将apache-discovery.1.service改为apache-discovery@%i.service。即如果我们有一个unit文件apache@8888.service,它将需要一个从服务apache-discovery@8888.service。%i 曾被 实例化的标识符(即8888) 替换过。在这个例子中,%i也可以被用来指代服务的一些信息,比如apahce server运行时占用的端口。

为了使其工作,我们改变了docker run的参数,将container的80端口,映射给主机的某一个端口。在静态的unit 文件中,我们使用${COREOS_PUBLIC_IPV4}:80:80,将container的80端口,映射到主机${COREOS_PUBLIC_IPV4}网卡的80端口。在这个template 文件中,我们使用${COREOS_PUBLIC_IPV4}:%i:80,使用%i来说明我们使用哪个端口。在template文件中选择合适的instance identifier会带来很大的灵活性。

container的名字被改为了基于instance ID的apache.%i记住container的名字不能够使用@标识符。现在,我们校正了运行container用到的所有指令(参数)。

在X-Fleet小节,我们同样改变了部署信息来替代原先的配置。

Sidekick Unit as a Template

将从unit文件模板化也是类似的过程,新的从unit文件叫apache-discovery@.service,内容如下:

[Unit]
Description=Apache web server on port %i etcd registration

# Requirements
Requires=etcd.service
Requires=apache@%i.service

# Dependency ordering and binding
After=etcd.service
After=apache@%i.service
BindsTo=apache@%i.service

[Service]

# Get CoreOS environmental variables
EnvironmentFile=/etc/environment

# Start
## Test whether service is accessible and then register useful information
ExecStart=/bin/bash -c '\
  while true; do \
    curl -f ${COREOS_PUBLIC_IPV4}:%i; \
    if [ $? -eq 0 ]; then \
      etcdctl set /services/apache/${COREOS_PUBLIC_IPV4} \'{"host": "%H", "ipv4_addr": ${COREOS_PUBLIC_IPV4}, "port": %i}\' --ttl 30; \
    else \
      etcdctl rm /services/apache/${COREOS_PUBLIC_IPV4}; \
    fi; \
    sleep 20; \
  done'

# Stop
ExecStop=/usr/bin/etcdctl rm /services/apache/${COREOS_PUBLIC_IPV4}

[X-Fleet]
# Schedule on the same machine as the associated Apache service
X-ConditionMachineOf=apache@%i.service

当主unit和从unit都是静态文件的时候,我们已经讲述了如何将从unit绑定到主unit,现在我们来讲下如何将实例化的从 unit绑定到同样根据模板实例化的主unit。

我们知道,curl命令可以被用来检查服务的有效性,为了让其连接到正确的url,我们用instance ID来代替80端口。这是非常必要的,因为在主unit中,我们改变了container映射的端口。

我们改变了写入到etcd的端口信息,同样是使用instance id来替换80端口。这样,设置到etcd的json信息就可以是动态的。它将采集service服务所在主机的hostname,ip地址和端口信息。

最后,我们也更改了X-Fleet小节,我们需要确定这个进程和其对应的主unit运行在一台主机上。

Instantiating Units from Templates

实例化template文件有多种方法:

fleet和systemd都可以处理链接,我们可以创建模板文件的链接文件:

ln -s apache@.service apache@8888.service
ln -s apache-discovery@.service apache-discovery@8888.service

这将创建两个链接,叫做apache@8888.serviceapache-discovery@8888.service,每个文件都包含了fleet和systemd运行它们所需要的所有信息,我们可以使用fleetctl提交、加载和启动这些服务fleetctl start apache@8888.service apache-discovery@8888.service

如果你不想使用链接文件的方式,可以直接使用fleetctl来提交模板文件:fleetctl submit apache@.service apache-discovery@.service,你可以在运行时为其赋予一个instance identifier,比如:fleetctl start apache@8888.service apache-discovery@8888.service

这种方式不需要链接文件,然而一些系统管理员偏好于使用链接文件的方式。因为链接文件一旦创建完毕便可以在任意时刻使用,并且,如果你将所有的链接文件放在一个地方,便可以在同一时刻启动所有实例。

假设我们将静态unit文件全放在static目录下,模板文件放在templates目录下,根据模板文件创建的链接文件全放在instances目录下,你就可以用fleetctl start instances/*一次启动所有的实例。如果你的服务很多的话,这将非常方便。

小结

通过本章,相信你对如何创建unit文件以及fleet的使用有了深入的了解。利用unit文件中灵活的指令(参数),你可以将你的服务分布化,将相互依赖的服务部署在一起,并将它们的信息注册到etcd中。

本文已授权在dockerpool上发表,欢迎大家参与讨论!