《CNI specification》翻译
Overview
本文提出了一个通用的基于插件的Linux容器网络解决方案,容器网络接口,CNI。它脱胎于旨在满足大多数rtk网络设计的rtk Networking Proposal。
首先,我们对如下两个名词进行具体的定义:
- container可以认为是与Linux network namespace是同义的。而一个network namespace具体对应什么,则与具体的容器运行时有关:例如,在rtk中,每个pod运行在一个单独的network namespace中。但是在docker中,network namespace存在于每个独立的Docker容器中
- network代表了一组可以独立寻址并且可以互相交互的实体。这些实体既可以是一个单独的容器(如上所述),一台机器,或者其他什么网络设备(例如,一台路由器)。container可以加入一个或多个network,也可以从一个或多个network中移除。
本文旨在说明容器运行时和插件之间的接口。同时,可能还有些众所周知的字段,runtime也想传递给底层的插件,不过这些内容并不在本文中进行描述。
General consideration
首先容器运行时需要为container新建一个network namespace。之后,它需要决定该container属于哪些network,对于每个network还需要确定对应执行哪些插件。network configuration是以JSON格式存在的,很容易被存储在文件中。配置中需要包含一些必须的字段,例如,"name",“type”以及相应的插件必须的字段。同时,network configuration允许其中的字段在不同的调用间发生改变。因此,存在一个可选的"args"字段用于存放异变的信息。最后,容器运行时通过顺序地调用相应的插件来创建相应的network。当container的生命周期结束时,运行时再以相反的顺序调用插件,将它们从networks中移除。
CNI Plugin
Overview
每个CNI插件都是以一个能被容器管理系统(比如,rkt或者Docker)调用的可执行文件的形式存在的。
CNI插件负责将一个network interface插入container network namespace(比如,veth pair的其中一端)并且在宿主机中做一些必要的配置(例如将veth的另一端加入bridge中)。接着通过调用适当的IPAM插件,将IP赋给interface并且设置路由。
Parameters
CNI插件支持如下三种操作:
- 将container加入network(Add):
- Parameters:
- Version. 调用者使用的CNI 配置的版本信息
- Container ID. 这个字段是可选的,但是建议使用,在容器活着的时候要求该字段全局唯一的。比如,存在IPAM的环境可能会要求每个container都分配一个独立的ID,这样每一个IP的分配都能和一个特定的容器相关联。例如,在appc implementations中,container ID其实就是pod ID
- Network namespace path. 这个字段表示要加入的network namespace的路径。例如,/proc/[pid]/ns/net或者对于该目录的bind-mount/link。
- Network configuration. 这是一个JSON文件用于描述container可以加入的network,具体内容在下文中描述
- Extra arguments. 该字段提供了可选的机制,从而允许基于每个容器进行CNI插件的简单配置
- Name of the interface inside the container. 该字段提供了在container (network namespace)中的interface的名字;因此,它也必须符合Linux对于网络命名的限制
- Result:
- Interface list. 根据插件的不同,这个字段可以包括sandbox (container or hypervisor) interface的name,以及host interface的name,每个interface的hardware address,以及interface所在的sandbox(如果存在的话)的信息。
- IP configuration assigned to each interface. IPv4和/或者IPv6地址,gateways以及为sandbox或host interfaces中添加的路由
- DNS inormation. 包含nameservers,domains,search domains和options的DNS information的字典
- Parameters:
- 将container从network中删除(Delete):
- Parameter:
- Version. 调用者使用的CNI 配置的版本信息
- ContainerID. 定义同上
- Network namespace path. 定义同上
- Network configuration. 定义同上
- Extra argument. 定义同上
- Name of the interface inside the container. 定义同上
- Parameter:
- 版本信息
- Parameter: 无
- Result: 返回插件支持的所有CNI版本
{
"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
}
最终executable command-line API会以network的type作为名字去调用相应的插件。它首先会在一系列预先定义好的目录中查找该可执行文件。一旦找到,它就会用以下的环境变量作为参数去调用该可执行文件:
- CNI_COMMAND: 表示进行的操作;ADD, DEL或者VERSION
- CNI_CONTAINERID: Container ID
- CNI_NETNS: network namespace文件的路径
- CNI_IFNAME: 创建的interface的名字,插件必须使用这个名字,否则返回错误
- CNI_ARGS: 在调用时用户传入的额外的参数,由以分号分割的,字母数字键值对组成,例如,"FOO=BAR;ABC=123"
- CNI_PATH:用于查找CNI插件的可执行文件的路径列表,在Linux中,路径之间由":"分割,在Windows中用";"分割
以JSON形式存在的network configuration将以stdin的方式进入插件。这意味着它并不和磁盘上某个特定的文件绑定,因此它所包含的信息也能在每次调用之后发生改变
Result
需要注意的是IPAM插件返回的是一个精简的Result结构,对它的描述放在IP Allocation中
当执行的是ADD命令时,如果返回值是0,并且有如下的JSON输出到stdout,那么说明执行成功了。在IPAM插件返回的结果中同样需要对ips和dns字段进行适当的填充,但是interface字段除外,因为IPAM插件并不应该意识到interface的存在
{
"cniVersion": "0.3.1",
"interfaces": [ (this key omitted by IPAM plugins)
{
"name": "<name>",
"mac": "<MAC address>", (required if L2 addresses are meaningful)
"sandbox": "<netns path or hypervisor identifier>" (required for container/hypervisor interfaces, empty/omitted for host interfaces)
}
],
"ips": [
{
"version": "<4-or-6>",
"address": "<ip-and-prefix-in-CIDR>",
"gateway": "<ip-address-of-the-gateway>", (optional)
"interface": <numeric index into 'interfaces' list>
},
...
],
"routes": [ (optional)
{
"dst": "<ip-and-prefix-in-cidr>",
"gw": "<ip-of-next-hop>" (optional)
},
...
]
"dns": {
"nameservers": <list-of-nameservers> (optional)
"domain": <name-of-local-domain> (optional)
"search": <list-of-additional-search-domains> (optional)
"options": <list-of-options> (optional)
}
}
cniVersion以Semantic Version 2.0的格式指定了插件使用的CNI版本。interfaces描述了插件创建的network interfaces。如果指定了CNI_IFNAME,那么插件必须用该名字对sandbox/hypervisor interface进行命名,否则返回错误
- mac (string):interface的hardware address。如果对于插件来说L2地址是没有意义的,那个该字段是可选的
- sandbox (string):container/namespace-based environment需要返回sandbox所在的network namespace的完整路径。Hypervisor/VM-based插件需要返回一个唯一的ID,代表新建的interface所在的virtualized sandbox。
ips字段包含了一系列的IP配置信息,详情参见IP well-known structure。dns字段包含了一个由通用的DNS信息组成的字典。详情参见DNS well-known structure。specification中并没有这些信息到底应该被如何使用。例如产生一个/etc/resolv.conf文件插入容器文件系统中,或者在宿主机运行一个DNS forwarder。
如果遇到错误将得到一个非零的返回值以及如下形式的JSON输出:
{
"cniVersion": "0.3.1",
"code": <numeric-error-code>,
"msg": <short-error-message>,
"details": <long-error-message> (optional)
}
cniVersion以Semantic Version 2.0的格式指定了插件使用的CNI版本。Error codes的0-99用于一些众所周知的错误(详情参见Well-known Error Codes)。超过100的值可以用于插件特定的错误。
另外,stderr可以用于输出一些unstructured output,例如logs。
Network Configuration
network configuration以JSON格式进行描述。configuration可以被存储在磁盘中或者通过容器运行时以其他方式产生。接下来是一些比较重要的字段:
- cniVersion(string):cniVersion以Semantic Version 2.0的格式指定了插件使用的CNI版本
- name (string):Network name。这应该在整个管理域中都是唯一的
- type (string):代表了CNI插件可执行文件的文件名
- args (dictionary):由容器运行时提供的可选的参数。比如,可以将一个由label组成的dictionary传递给CNI插件,通过在args下增加一个labels字段
- ipMasqs (boolean):可选项(如果插件支持的话)。为network在宿主机创建IP masquerade。这个字段是必须的,如果需要将宿主机作为网关,从而能够路由到容器分配的IP
- ipam:由特定的IPAM值组成的dictionary
- type (string):表示IPAM插件的可执行文件的文件名
- dns:由特定的DNS值组成的dictionary
- nameservers (list of strings):一系列对network可见的,以优先级顺序排列的DNS nameserver列表。列表中的每一项都包含了一个IPv4或者一个IPv6地址
- domain (string):用于查找short hostname的本地域
- search (list of strings):以优先级顺序排列的用于查找short domain的查找域。对于大多数resolver,它的优先级比domain更高
- options(list of strings):一系列可以被传输给resolver的可选项
插件可能会定义它们自己能接收的额外的字段,但是遇到一个未知的字段可能会产生错误。例外的是args字段,它可以被用于传输一些额外的字段,但可能会被插件忽略
Example configurations
{
"cniVersion": "0.3.1",
"name": "dbnet",
"type": "bridge",
// type (plugin) specific
"bridge": "cni0",
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
{
"cniVersion": "0.3.1",
"name": "pci",
"type": "ovs",
// type (plugin) specific
"bridge": "ovs0",
"vxlanID": 42,
"ipam": {
"type": "dhcp",
"routes": [ { "dst": "10.3.0.0/16" }, { "dst": "10.4.0.0/16" } ]
}
// args may be ignored by plugins
"args": {
"labels" : {
"appVersion" : "1.0"
}
}
}
{
"cniVersion": "0.3.1",
"name": "wan",
"type": "macvlan",
// ipam specific
"ipam": {
"type": "dhcp",
"routes": [ { "dst": "10.0.0.0/8", "gw": "10.0.0.1" } ]
},
"dns": {
"nameservers": [ "10.0.0.1" ]
}
}
Network Configuration Lists
Network configuration lists能够以指定顺序允许多个CNI插件,并且将每个插件的允许结果传递给下一个插件。列表中包含了一些众所周知的字段以及由一个或多个标准的CNI network configuration组成的列表(如上所示)。
列表以JSON格式描述,可以储存在磁盘中,也可以由容器运行时以其他方式产生。接下来的这些字段是众所周知的并且有对应的含义:
- cniVersion(string):以Semantic Version 2.0描述的CNI版本,对此整个configuration list以及每个单独的configuraion必须遵从
- name (string):Network name。这应该在整个管理域中都是唯一的
- plugins (lists):一系列标准的CNI network configuration dictionary (如上所示)
当执行插件列表时,运行时必须用列表的name和cniVersion字段替代每个network configuraion的name和cniVersion字段。这确保了列表中插件的name和CNI版本都是一致的,从而避免插件之间产生版本冲突。如果插件通过network configuration的capability字段说明它支持某种specific capability,那么运行时必须将capability-based keys以map的形式插入插件的config JSON的runtimeConfig字段中。同时,传给runtimeConfig的key必须和network configuration的capabilities key的名字相同。
对于ADD操作,运行时必须添加一个prevResult字段到下一个插件的configuration JSON中,并且它的内容就是上一个插件的以JSON格式描述的结果。并且每个插件都必须将preResult的内容输出到stdout从而让后续的插件或者运行时可以获取该结果,除非,它们想要修改或限制之前的结果。插件是允许修改或限制全部或者部分的prevResult内容的。然而对于支持包含prevResult的CNI版本的插件,它必须显式地通过,修改或者限制prevResult,但是忽略该字段是不允许的。
同时,运行时必须在同一环境下执行列表中的每个插件
对于DEL操作,运行时必须以相反的顺序执行插件列表
Network Configuration List Error Handling
当在执行插件列表时发生了错误,那么运行时必须停止执行。如果ADD操作执行失败了,当运行时要处理错误时,它需要以和ADD相反的顺序对列表中的插件执行DEL操作,即使其中某些插件在ADD操作中还为被调用。
插件必须完整地执行DEL操作并不报错,即使有些资源缺失了。比如,对于IPAM插件,即使container network namespace 已经不存在了,它仍然会释放IP allocation并且成功返回,除非network namespace对于IPAM特别重要。尽管DHCP会向container network interface发送一个'release' message,但是因为DHCP leases都是有生命周期的,因此release操作并没有那么重要,也就不应该返回错误。另外,对于bridge插件即使container network namespace和/或者container network interface已经不存在了,它也要调用IPAM插件的DEL操作并且删除相应的资源(如果有的话)。
Example network configuration lists
{
"cniVersion": "0.3.1",
"name": "dbnet",
"plugins": [
{
"type": "bridge",
// type (plugin) specific
"bridge": "cni0",
// args may be ignored by plugins
"args": {
"labels" : {
"appVersion" : "1.0"
}
},
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
},
{
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
}
}
]
}
Network configuration list runtime examples
基于上述的network configuraion list,容器运行时需要执行以下步骤来完成ADD操作。需要注意的是,运行时会将configuration list中的cniVersion和name字段添加到每个插件的configuration中,从而保证列表中所有插件的版本和名字一致。
1、首先以如下格式调用bridge插件
{
"cniVersion": "0.3.1",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
"args": {
"labels" : {
"appVersion" : "1.0"
}
},
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
2、接着以如下的JSON调用tuning插件,其中的prevResult字段包含了bridge插件返回的结果
{
"cniVersion": "0.3.1",
"name": "dbnet",
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
},
"prevResult": {
"ips": [
{
"version": "4",
"address": "10.0.0.5/32",
"interface": 0
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}
给定同样的network configuraion list,容器运行时会以如下步骤完成DEL操作。需要注意的是,并不需要prevResult字段,因为DEL操作并不返回任何result。另外,插件的执行顺序和ADD是相反的。
1、首先以如下JSON调用tuning插件
{
"cniVersion": "0.3.1",
"name": "dbnet",
"type": "tuning",
"sysctl": {
"net.core.somaxconn": "500"
}
}
2、接着以如下JSON调用bridge插件
{
"cniVersion": "0.3.1",
"name": "dbnet",
"type": "bridge",
"bridge": "cni0",
"args": {
"labels" : {
"appVersion" : "1.0"
}
},
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
IP Allocation
作为整个操作的一部分,CNI插件需要给interface分配并维护一个IP地址,并且还要安装一些和该interface有关的必要的路由。这给了CNI插件很大的灵活性同时也给它造成了很大的负担。许多插件需要重复编写多种用户想要的IP管理框架(例如,dhcp, host-local)。为了减轻各个插件的负担,并且将IP管理的功能独立出来,我们定义了第二种插件类型 -- IP Address Management 插件(IPAM插件)。此时,其他插件的任务就是在适当的执行过程中调用相应的IPAM插件。IPAM插件用于确定interface的IP/子网,网关,路由并且将这些信息返回"main" plugin去执行。IPAM插件可以从一个协议(如dhcp)中,或者从本地文件系统存储的数据中,或者network configuration file中的"ipam"字段,或者上述这些方式的组合中获取信息。
IP Address Management (IPAM) Interface
和CNI插件类似,IPAM插件也是以运行可执行文件的方式被调用的。可执行文件将会在一些预先定义的路径列表中查找,通过CNI_PATH指定。IPAM插件将获得所有传输给CNI插件的环境变量,并且和CNI插件一样,IPAM通过stdin获取network configuration
对于ADD命令,如果返回值为0,并且stdout中有如下的JSON格式,说明执行成功
{
"cniVersion": "0.3.1",
"ips": [
{
"version": "<4-or-6>",
"address": "<ip-and-prefix-in-CIDR>",
"gateway": "<ip-address-of-the-gateway>" (optional)
},
...
],
"routes": [ (optional)
{
"dst": "<ip-and-prefix-in-cidr>",
"gw": "<ip-of-next-hop>" (optional)
},
...
]
"dns": {
"nameservers": <list-of-nameservers> (optional)
"domain": <name-of-local-domain> (optional)
"search": <list-of-search-domains> (optional)
"options": <list-of-options> (optional)
}
}
与常规的CNI插件不同的是,IPAM插件返回的是简化的Result结构,其中不包括interfaces字段,因为IPAM插件不应该关注它们的父插件配置的interfaces,那些有特殊要求的IPAM插件除外(例如,dhcp IPAM插件)。
ips字段包含了一系列的IP配置信息,详情参见IP well-known structure
dns字段包含了一个由通用的DNS信息组成的字典。详情参见DNS well-known structure
返回的Errors和logs的和CNI插件相同。详情参见CNI Plugin Result
IPAM插件的例子如下:
- host-local:在一个特定的范围内选择一个未被其他container使用的IP
- dhcp:使用DHCP协议获取并且维护一个IP的租用。DHCP request会通过刚创建的container interface发送出来,因此相关的network必须支持广播
Notes:
- 路由应该被配置为0 metric
- 默认路由应该配置为"0.0.0.0/0"。因为其他的network可能已经配置了默认路由,CNI插件必须能够跳过已有的默认配置
Well-known Structures
IPs:
"ips": [
{
"version": "<4-or-6>",
"address": "<ip-and-prefix-in-CIDR>",
"gateway": "<ip-address-of-the-gateway>", (optional)
"interface": <numeric index into 'interfaces' list> (not required for IPAM plugins)
},
...
]
ips字段是一个由插件决定的IP配置列表。每个条目都是都是一个dictionary,描述了一个network interface的IP配置。多个network interface的IP配置和单个interface上的多个IP配置都将以ips列表中的不同条目返回。插件已知的所有特性都要提供,即使并不是严格必须的:
- version (string):"4"或者"6",代表了条目中IP地址的版本。所有的IP地址和网关地址都要符合相应的版本
- address (string):CIDR形式的IP地址(例如,"192.168.1.3/24")
- gateway (string):对应子网的默认网关,如果存在的话。并不要求CNI插件添加任何与网关相关的路由。路由是单独通过routes字段指定的。一个使用该字段的例子是,CNI bridge插件将该地址添加到Linux bridge中,将其作为网关。
- interface (uint):该值表示,此IP配置需要作用的interface,在返回的结果JSON中的interfaces字段中对应的下标。IPAM插件不能返回这个值,因为它们没有任何关于network interface的信息
Routes:
"routes": [
{
"dst": "<ip-and-prefix-in-cidr>",
"gw": "<ip-of-next-hop>" (optional)
},
...
]
每个routes字段由以下内容组成。所有的IP地址在routes中一定要有相同的IP版本,4或者6
- dst (string):以CIDR描述的目标子网
- gw (string):网关的IP地址。如果该字段不存在,则假设为默认网关(由CNI插件决定)
DNS:
"dns": {
"nameservers": <list-of-nameservers> (optional)
"domain": <name-of-local-domain> (optional)
"search": <list-of-additional-search-domains> (optional)
"options": <list-of-options> (optional)
}
dns字段包含了由一些通用的DNS信息组成的dictionary
- nameservers (list of strings):一系列对network可见的,以优先级顺序排列的DNS nameserver列表。列表中的每一项都包含了一个IPv4或者一个IPv6地址
- domain (string):用于查找short hostname的本地域
- search (list of strings):以优先级顺序排列的用于查找short domain的查找域。对于大多数resolver,它的优先级比domain更高
- options (list of strings):一系列可以被传输给resolver的可选项
Well-known Error Codes
- 1 - CNI版本不兼容
- 2 - network configuration存在不支持的字段。错误信息中必须包含不支持字段的key和value
《CNI specification》翻译的更多相关文章
- 《OVN Logical Flows and ovn-trace》翻译
在本篇文章中,我将解释什么是Logical Flow以及如何使用ovn-trace去更好地理解它们.同时,我也会用一些例子来解释,为什么使用Logical Flow这种抽象模型能让新特性的添加变得出乎 ...
- OVN实战---《The OVN Load Balancer》翻译
Overview 基于前面几篇文章的基础之上,我们接下来将要探索OVN中的load balancingz这一特性.但是在开始之前,我们先来回顾一下上一个lab中创建好的拓扑结构. The lab ne ...
- OVN实战---《The OVN Gateway Router》翻译
Overview 在本文中我将在前文的基础上添加一个OVN gateway router.gateway router将使得lab network能访问我们的overlay network The l ...
- ovs ovn 学习资料
0.A Primer on OVN http://blog.spinhirne.com/2016/09/a-primer-on-ovn.html 1.Open Virtual Networking W ...
- OVN实战---《OVN and Containers》翻译
Overview 在本篇文章中,我们要讨论的是OVN和容器的集成.到本次实验中,我们将会创建一个包含有一对容器的“虚拟机”,这些容器会直接和OVN logical switch相连,并且可以供逻辑网络 ...
- OVN实战---《A Primer on OVN》翻译
overview 在本文中,我们将在三个host之间创建一个简单的二层overlay network.首先,我们来简单看一下,整个系统是怎么工作的.OVN基于分布式的control plane,其中各 ...
- OVN架构翻译
概述 ovn-controller是OVN在虚拟机上的agent,北向连接OVN的南向数据库,学习OVN的配置和状态,并使用虚拟机的状态来填充PN表以及Binding表的Chassis列:南向连接op ...
- OVN架构
原文地址 OVN架构 1.简介 OVN,即Open Virtual Network,是一个支持虚拟网络抽象的系统. OVN补充了OVS的现有功能,增加了对虚拟网络抽象的原生(native)支持,比如虚 ...
- 如何借助 OVN 来提高 OVS 在云计算环境中的性能
众所周知,OpenvSwitch 以其丰富的功能和不错的性能,已经成为 Openstack 部署中最受欢迎的虚拟交换机.由于 Openstack Neutron 的架构引入了一些性能问题,比如 neu ...
- OVN入门
参考链接 如何借助 OVN 来提高 OVS 在云计算环境中的性能 OVN简介 Open vSwitch Documentation OVSDB介绍及在OpenDaylight中的调用 OpenDayl ...
随机推荐
- error: Semantic Issue: Interface type cannot be statically allocated
转自:http://hongmin118.iteye.com/blog/1333524 error: Semantic Issue: Interface type cannot be statical ...
- 前端点击删除按钮删除table表格的数据
table.on('tool(hostTable)', function (obj) { var data = obj.data;//须写 if (obj.event === 'del') { var ...
- QT界面 使用QStyledItemDelegate QPainter QStyleOptionViewItem QModelIndex组合实现项的绘制
QStyledItemDelegate类为来自模型的数据项提供了显示和编辑工具. 当在Qt项视图(例如QTableView)中显示来自模型的数据时,各个项由委托(delegate)绘制.此外,当编辑一 ...
- JavaScript学习日志(1)
javascript用法: 1.HTML中的脚本必须位于<script>与</script>标签之间,可被放置在HTML页面的<body>和<head> ...
- 基于Java语言开发jt808、jt809技术文章精华索引
很多技术开发人员喜欢追逐最新的技术,如Node.js, go等语言,这些语言只是解决了某一个方面,如只是擅长异步高并发等等,却在企业管理后台开发方面提供的支持非常不够,造成项目团队技术选项失败,开发后 ...
- js上传控件 plupload 使用记录
最近一个项目需要使用一个上传控件进行多图片上传,给用户更好的体验,找到了plupload,用了一下感觉还是不错的, 1.从官网上 可以获得例子 ,我集成到了jsp,如下: <%@ page l ...
- C运行库和VC对应关系
## C运行库和VC对应关系----------------------------------------------------------------Msvcr60.DLL -- VC6Msvc ...
- P3P解决cookie跨域
P3P是什么 P3P(Platform for Privacy Preferences)是W3C公布的一项隐私保护推荐标准,以为用户提供隐私保护. P3P标准的构想是:Web 站点的隐私策略应该告 ...
- nginx调用php-fpm出错解决方法和nginx配置详解
装完了nginx和php-5.5,配置好了nginx调用php后,就开始启动php-fpm. 使用下面的命令 复制代码 代码如下: /usr/local/php/sbin/php-fpm 就可以启动了 ...
- Nexus 5 刷机 - Android 5.0 Lollipop
Nexus刷机 : 官方地址 刷机步骤 下载相应的安装包 连接USB 重启手机,进入BootLoader界面 : 使用命令 adb reboot bootloader 关机; 音量键下 + 电源键 ...