Kubernetes Cookbook 编程指南 中文版教程
创建时间:2018-12-07  访问量:3665  7  0

Kubernetes Cookbook 编程指南 中文版教程

使用AWS CloudFormation快速供给

AWS CloudFormation是一个服务,它能使创建AWS资源更容易。一个简单的JSON格式文本文件只需要单击几下就能创建应用的基础设施。系统管理员和开发者可以很容易的创建、更新和管理AWS的资源,并且不需要担心人为的错误。在本节,我们将利用本章前面章节中的内容,使用CloudFormation来创建它们并使用Kubernetes自动化设置运行实例。

开始

CloudFormcation的单是一个栈。一个栈是由一个CloudFormation 模板创建的,这个模板是AWS资源中列出的JSON格式的文本文件。在AWS控制台中使用CloudFormation模板运行一个CloudFormation栈之前,我们先对CloudFormation控制上的tab名称有一个深入的了解:

Tab name Description
Overview Stack profile overview. Name, status and description are listed here
Output The output fields of this stack
Resources The resources listed in this stack
Events The events when doing operations in this stack
Template Text file in JSON format
Parameters The input parameters of this stack
Tags AWS tags for the resources
Stack Policy Stack policy to use during update. This can prevent you from removing or updating resources accidentally

一个CloudFormcation模板包含许多部分;如所示例模板所述:

{
	"AWSTemplateFormatVersion": "AWS CloudFormation templateversion
date",
	"Description": "stack description",
	"Metadata": {
		#putadditionalinformationforthistemplate
	},
	"Parameters": {
		#user-specifiedtheinputofyourtemplate
	},
	"Mappings": {
		#usingfordefineconditionalparametervaluesanduseitinthetemplate
	},
	"Conditions": {
		#usetodefinewhethercertainresourcesarecreated,
		configuredinacertaincondition.
	},
	"Resources": {
		#majorsectioninthetemplate,
		usetocreateandconfigureAWSresources
	},
	"Outputs": {
		#user-specifiedoutput
	}
}

我们将使用这三个主要部分:

  • Parameters

  • Resources

  • Outputs

当创建栈时,Parameters可能是你想要输入的变量,Resources是声明AWS资源设置的一个主要部分,Outputs是可能想要暴露到CloudFormcation UI的部分,因此,当部署一个模板时很容易发现一个资源的输出信息。

Intrinsic Function是AWS CloudFormation的内建函数。它们提供了强大功能使用资源能链接在一起。这是一个常见的用例,您需要将多个资源链接在一起,但它们会相互了解直到运行时。在这种情况下,内部函数可以完美的解决这个问题。CloudFormation中提供了几个内建函数。在这种情况下,我们将使用Fn::GetAtt,Fn::GetAZs和Ref。

相关描述见下表:

Functions Description Usage
Fn::GetAtt Retrieve a value of an attribute from a resource {"Fn::GetAtt" : [ "logicalNameOfResource", "attributeName" ]}
Fn::GetAZs Return a list of AZs for the region {"Fn::GetAZs" : "us east-1"}
Fn::Select Select a value from a list { "Fn::Select" : [ index, listOfObjects ]}
Ref Return a value from a logical name or parameter {"Ref" : "logicalName"}

如何去做

我们并不是使用一千行来启动一些模板,而是将它们分为两个部分:一个仅是资源,另一个仅是应用的网络。在我GitHub仓库https://github.com/kubernetes-cookbook/cloudformation 中,这两个模板都是可用的。

创建一个网络基础设施

让我们回顾一下在AWS中构建Kubernetes基础设施这一节。我们将创建一个10.0.0.0/16的VPC,包含两个公有子网和两个私有子网。除了这些,我们还将创建一个网关并向公有子网添加路由表规则,以可以使数据流到外部网络。我们也将创建一个NAT网关,它会在公共子网分配一个弹性IP,为了保证一个私有子网可以访问因特网:

怎么做呢?在模板的开始,我们将定义两个参数:一个是Prefix,另一个CIDRPrefix.Prefix,它们是用于将要创建的资源名称的前缀。CIDRPrefix是一个我们想要创建的IP地址的两个部分。黙认是10.0。我们也将设置一个长度限制:

"Parameters": {
	"Prefix": {
		"Description": "Prefix of resources",
		"Type": "String",
		"Default": "KubernetesSample",
		"MinLength": "1",
		"MaxLength": "24",
		"ConstraintDescription": "Length is too long"
	},
	"CIDRPrefix": {
		"Description": "Network cidr prefix",
		"Type": "String",
		"Default": "10.0",
		"MinLength": "1",
		"MaxLength": "8",
		"ConstraintDescription": "Length is too long"
	}
}

然后,我们开始描述Resources部分。对于详细的资源类型和属性,建议你访问AWS文档http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resourcetype-ref.html

"VPC": {
	"Type": "AWS::EC2::VPC",
	"Properties": {
		"CidrBlock": {
			"Fn::Join": [".",
			[{
				"Ref": "CIDRPrefix"
			},
			"0.0/16"]]
		},
		"EnableDnsHostnames": "true",
		"Tags": [{
			"Key": "Name",
			"Value": {
				"Fn::Join": [".",
				[{
					"Ref": "Prefix"
				},
				"vpc"]]
			}
		},
		{
			"Key": "EnvName",
			"Value": {
				"Ref": "Prefix"
			}
		}]
	}
}

这里,我们将使用逻辑名称VPC和类型AWS::EC2::VPC来创建一个资源。请注意逻辑名称是非常重要的,并且它在模板中不能重复。你可以在模板中的任何其它资源中使用{"Ref":"VPC"}来引用VPCId。这个VPC的名称将是$Prefix.vpc和CIDR $CIDRPrefix.0.0/16这种形式。下面的图片显示了一个已创建的VPC:

接下来,我们将创建第一个使用CIDR $CIDRPrefix.0.0/24的公共子网。注意到{Fn::GetAZs:""}将会返回所有可用AZ的列表。我们将使用Fn::Select来选择索引为0的第一个元素:

"SubnetPublicA": {
	"Type": "AWS::EC2::Subnet",
	"Properties": {
		"VpcId": {
			"Ref": "VPC"
		},
		"CidrBlock": {
			"Fn::Join": [".",
			[{
				"Ref": "CIDRPrefix"
			},
			"0.0/24"]]
		},
		"AvailabilityZone": {
			"Fn::Select": ["0",
			{
				"Fn::GetAZs": ""
			}]
		},
		"Tags": [{
			"Key": "Name",
			"Value": {
				"Fn::Join": [".",
				[{
					"Ref": "Prefix"
				},
				"public",
				"subnet",
				"A"]]
			}
		},
		{
			"Key": "EnvName",
			"Value": {
				"Ref": "Prefix"
			}
		}]
	}
}

第二个公共子网和两个私有子网都与第一个相同,只是CIDR $CIDRPrefix.1.0/24不同。公有子网与私有子网之间的不同是是否能够访问因特网。典型的,在公有子网中的一个实例将会有一个公网IP或一个弹性IP可以用于访问因特网的。然而,一个私有子网是不可以访问因特网的,除了使用一个堡垒主机或通过VPN。在AWS的设置中的不同是路由表中的路由。为了让你的实例与因特网通信,我们应该为公有子网创建一个网关并为一个私有子网创建一个NAT网关:

"InternetGateway": {
	"Type": "AWS::EC2::InternetGateway",
	"Properties": {
		"Tags": [{
			"Key": "Stack",
			"Value": {
				"Ref": "AWS::StackId"
			}
		},
		{
			"Key": "Name",
			"Value": {
				"Fn::Join": [".",
				[{
					"Ref": "Prefix"
				},
				"vpc",
				"igw"]]
			}
		},
		{
			"Key": "EnvName",
			"Value": {
				"Ref": "Prefix"
			}
		}]
	}
},
"GatewayAttachment": {
	"Type": "AWS::EC2::VPCGatewayAttachment",
	"Properties": {
		"VpcId": {
			"Ref": "VPC"
		},
		"InternetGatewayId": {
			"Ref": "InternetGateway"
		}
	}
}

我们将使用名为$Prefix.vpc.igw和一个逻辑名为InternetGateway声明一个网关;也将它附加了VPC中。然后,创建NatGateway。NatGateway黙认需要一个EIP,因此,我们先要创建它,并使用DependsON函数来告诉CloudFormation NatGateway资源必须在NatGatewayEIP创建之后创建。注意到在NatGateway的属性中有一个AllocationId而不是网关ID。我们将使用内建函数Fn::GetAtt来从NatGatwayEIP资源中获取AllocationId属性:

"NatGatewayEIP": {
	"Type": "AWS::EC2::EIP",
	"DependsOn": "GatewayAttachment",
	"Properties": {
		"Domain": "vpc"
	}
},
"NatGateway": {
	"Type": "AWS::EC2::NatGateway",
	"DependsOn": "NatGatewayEIP",
	"Properties": {
		"AllocationId": {
			"Fn::GetAtt": ["NatGatewayEIP",
			"AllocationId"]
		},
		"SubnetId": {
			"Ref": "SubnetPublicA"
		}
	}
}"RouteTableInternet": {
	"Type": "AWS::EC2::RouteTable",
	"Properties": {
		"VpcId": {
			"Ref": "VPC"
		},
		"Tags": [{
			"Key": "Stack",
			"Value": {
				"Ref": "AWS::StackId"
			}
		},
		{
			"Key": "Name",
			"Value": {
				"Fn::Join": [".",
				[{
					"Ref": "Prefix"
				},
				"internet",
				"routetable"]]
			}
		},
		{
			"Key": "EnvName",
			"Value": {
				"Ref": "Prefix"
			}
		}]
	}
}

那私有子网呢?你可以使用相同的声明;只要将逻辑名称改为RouteTableNat即可。在创建一个路由表之后,再创建路由:

"RouteInternet": {
	"Type": "AWS::EC2::Route",
	"DependsOn": "GatewayAttachment",
	"Properties": {
		"RouteTableId": {
			"Ref": "RouteTableInternet"
		},
		"DestinationCidrBlock": "0.0.0.0/0",
		"GatewayId": {
			"Ref": "InternetGateway"
		}
	}
}

这是公有子网路由表中的路由。如果目标CIDR是0.0.0.0/0,那么它将被重新分配到RouteTableInternte表中并将数据包路由到InternetGatway。我们来看一下私有子网路由:

"RouteNat": {
	"Type": "AWS::EC2::Route",
	"DependsOn": "RouteTableNat",
	"Properties": {
		"RouteTableId": {
			"Ref": "RouteTableNat"
		},
		"DestinationCidrBlock": "0.0.0.0/0",
		"NatGatewayId": {
			"Ref": "NatGateway"
		}
	}
}

它与RouteInternet基本相同,除了将数据包路由到NatGateway 0.0.0.0、0,如果有的话,等下,子网与一个路由表之间是什么关系?我们没有看到任何的声明表明一个确定子网中的规则。我们需要使用SubnetRouteTableAssociation来定义它们之间的关系。下面的示例定义了公有子网与私有子网;你也可能通过复制添加第二个公有/私有子网:

"SubnetRouteTableInternetAssociationA": {
	"Type": "AWS::EC2::SubnetRouteTableAssociation",
	"Properties": {
		"SubnetId": {
			"Ref": "SubnetPublicA"
		},
		"RouteTableId": {
			"Ref": "RouteTableInternet"
		}
	}
},
"SubnetRouteTableNatAssociationA": {
	"Type": "AWS::EC2::SubnetRouteTableAssociation",
	"Properties": {
		"SubnetId": {
			"Ref": "SubnetPrivateA"
		},
		"RouteTableId": {
			"Ref": "RouteTableNat"
		}
	}
}

我们已经完成网络基础设施。然后,我们通过AWS控制台来运行它。首先,仅单击运行一个栈并选择VPC样例模板。

单击next;你将看到参数页面。它有自己的黙认值,但你可以在创建或更新栈的时候改变它。

在单击finish后,CloudFormation将开始创建你在模板中声明的资源。完成后,它将返CREATE_COMPLETE的状态。

为应用管理创建OpsWorks

对于应用管理,我们将利用OpsWorks,它是AWS中的一个应用程序生命周期的管理。请参考前面两部分以了解更多关于OpsWorks和Chef。这里,我们将描述如何自动创建OpsWorks栈和相关的资源。

这里有8个参数。为Kubernetes Master和Etcd添加基本的认证:Kubernets要MasterBaAccount,K8sMasterBaPassword和EtcdBaPassword。我们也将VPC ID和私有子网ID作为这里的输入,这两个参数是在前面的例子中创建的。作为参数,我们可以使用UI中下拉列表中列出的AWS::EC2::VPC::Id类型。请参考AWS文档http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameterssection-structure.html 相关支持的类型:

"Parameters": {
	"Prefix": {
		"Description": "Prefix of resources",
		"Type": "String",
		"Default": "KubernetesSample",
		"MinLength": "1",
		"MaxLength": "24",
		"ConstraintDescription": "Length is too long"
	},
	"PrivateNetworkCIDR": {
		"Default": "192.168.0.0/16",
		"Description": "Desired Private Network CIDR or Flanneld (must
not overrap VPC CIDR)",
		"Type": "String",
		"MinLength": "9",
		"AllowedPattern": "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\
d{1,3}/\\d{1,2}",
		"ConstraintDescription": "PrivateNetworkCIDR must be IPv4
format"
	},
	"VPCId": {
		"Description": "VPC Id",
		"Type": "AWS::EC2::VPC::Id"
	},
	"SubnetPrivateIdA": {
		"Description": "Private SubnetA",
		"Type": "AWS::EC2::Subnet::Id"
	},
	"SubnetPrivateIdB": {
		"Description": "Private SubnetB",
		"Default": "subnet-9007ecc9",
		"Type": "AWS::EC2::Subnet::Id"
	},
	"K8sMasterBaAccount": {
		"Default": "admin",
		"Description": "The account of basic authentication for k8s
Master",
		"Type": "String",
		"MinLength": "1",
		"MaxLength": "75",
		"AllowedPattern": "[a-zA-Z0-9]*",
		"ConstraintDescription": "Account and Password should follow
Base64 pattern"
	},
	"K8sMasterBaPassword": {
		"Default": "Passw0rd",
		"Description": "The password of basic authentication for k8s
Master",
		"Type": "String",
		"MinLength": "1",
		"MaxLength": "75",
		"NoEcho": "true",
		"AllowedPattern": "[a-zA-Z0-9]*",
		"ConstraintDescription": "Account and Password should follow
Base64 pattern"
	},
	"EtcdBaPassword": {
		"Default": "Passw0rd",
		"Description": "The password of basic authentication for
Etcd",
		"Type": "String",
		"MinLength": "1",
		"MaxLength": "71",
		"NoEcho": "true",
		"AllowedPattern": "[a-zA-Z0-9]*",
		"ConstraintDescription": "Password should follow Base64
pattern"
	}
}

在开始使用OpsWorks栈之前,我们需要为它创建两个IAM角色。一个是服务解色,用来运行实例的,附加ELB后面等等。另一个是一个实例的角色,它是用来定义OpsWorks实例可以执行权限,以访问AWS资源。这里,我们不能从EC2访问任何AWS资源,因此我们只能创建一个骨架。请注意,当你使用IAM创建的CloudFormation时,需要有IAM权限。当运行栈时,单击下面的复选框:

SecurityGroup这一节,我将为一组机器定义入口和出口。我们将以Kubernetes Master作为示例。因为我们将ELB放在Master的前面,并且ELB能保持未来HA设置的灵活性。使用ELB,我们将需要为ELB创建一个安全组并指向Kubernetes Master的入口,这个入口与ELB安全组的8080端口和6443端口相关联。下面是Kubernetes Master安全组的一个示例;它向外界打开了80和8080端口:

"SecurityGroupELBKubMaster": {
	"Type": "AWS::EC2::SecurityGroup",
	"Properties": {
		"GroupDescription": {
			"Ref": "Prefix"
		},
		"SecurityGroupIngress": [{
			"IpProtocol": "tcp",
			"FromPort": "80",
			"ToPort": "80",
			"CidrIp": "0.0.0.0/0"
		},
		{
			"IpProtocol": "tcp",
			"FromPort": "8080",
			"ToPort": "8080",
			"SourceSecurityGroupId": {
				"Ref": "SecurityGroupKubNode"
			}
		}],
		"VpcId": {
			"Ref": "VPCId"
		},
		"Tags": [{
			"Key": "Application",
			"Value": {
				"Ref": "AWS::StackId"
			}
		},
		{
			"Key": "Name",
			"Value": {
				"Fn::Join": ["-",
				[{
					"Ref": "Prefix"
				},
				"SGElbKubMaster"]]
			}
		}]
	}
},

这里是Kubernetes Master实例设置的一个示例。它允许你从ELB的8080和6443端口接收流量。我们将为kubectl命令打开SSH端口:

"SecurityGroupKubMaster": {
	"Type": "AWS::EC2::SecurityGroup",
	"Properties": {
		"GroupDescription": {
			"Ref": "Prefix"
		},
		"SecurityGroupIngress": [{
			"IpProtocol": "tcp",
			"FromPort": "22",
			"ToPort": "22",
			"CidrIp": "0.0.0.0/0"
		},
		{
			"IpProtocol": "tcp",
			"FromPort": "8080",
			"ToPort": "8080",
			"SourceSecurityGroupId": {
				"Ref": "SecurityGroupELBKubMaster"
			}
		},
		{
			"IpProtocol": "tcp",
			"FromPort": "6443",
			"ToPort": "6443",
			"SourceSecurityGroupId": {
				"Ref": "SecurityGroupELBKubMaster"
			}
		}],
		"VpcId": {
			"Ref": "VPCId"
		},
		"Tags": [{
			"Key": "Application",
			"Value": {
				"Ref": "AWS::StackId"
			}
		},
		{
			"Key": "Name",
			"Value": {
				"Fn::Join": ["-",
				[{
					"Ref": "Prefix"
				},
				"SG-KubMaster"]]
			}
		}]
	}
}

请参考本书中关于etcd和node安全组设置的一些示例。接下来,我们开始创建这个OpsWorks栈。CustomJson作为Chef recipe的输入。如果Chef一开始不知道任何东西,你将需要向CustomJson参数传递如下内容:

"OpsWorksStack": {
	"Type": "AWS::OpsWorks::Stack",
	"Properties": {
		"DefaultInstanceProfileArn": {
			"Fn::GetAtt": ["RootInstanceProfile",
			"Arn"]
		},
		"CustomJson": {
			"kubernetes": {
				"cluster_cidr": {
					"Ref": "PrivateNetworkCIDR"
				},
				"version": "1.1.3",
				"master_url": {
					"Fn::GetAtt": ["ELBKubMaster",
					"DNSName"]
				}
			},
			"ba": {
				"account": {
					"Ref": "K8sMasterBaAccount"
				},
				"password": {
					"Ref": "K8sMasterBaPassword"
				},
				"uid": 1234
			},
			"etcd": {
				"password": {
					"Ref": "EtcdBaPassword"
				},
				"elb_url": {
					"Fn::GetAtt": ["ELBEtcd",
					"DNSName"]
				}
			},
			"opsworks_berkshelf": {
				"debug": true
			}
		},
		"ConfigurationManager": {
			"Name": "Chef",
			"Version": "11.10"
		},
		"UseCustomCookbooks": "true",
		"UseOpsworksSecurityGroups": "false",
		"CustomCookbooksSource": {
			"Type": "git",
			"Url": "https://github.com/kubernetes-cookbook/opsworksrecipes.git"
		},
		"ChefConfiguration": {
			"ManageBerkshelf": "true"
		},
		"DefaultOs": "Red Hat Enterprise Linux 7",
		"DefaultSubnetId": {
			"Ref": "SubnetPrivateIdA"
		},
		"Name": {
			"Ref": "Prefix"
		},
		"ServiceRoleArn": {
			"Fn::GetAtt": ["OpsWorksServiceRole",
			"Arn"]
		},
		"VpcId": {
			"Ref": "VPCId"
		}
	}
},

在创建完栈之后,我们可开始创建每个层了。以Kubernetes Master为示例:

"OpsWorksLayerKubMaster": {
	"Type": "AWS::OpsWorks::Layer",
	"Properties": {
		"Name": "Kubernetes Master",
		"Shortname": "kube-master",
		"AutoAssignElasticIps": "false",
		"AutoAssignPublicIps": "false",
		"CustomSecurityGroupIds": [{
			"Ref": "SecurityGroupKubMaster"
		}],
		"EnableAutoHealing": "false",
		"StackId": {
			"Ref": "OpsWorksStack"
		},
		"Type": "custom",
		"CustomRecipes": {
			"Setup": ["kubernetes-rhel::flanneld",
			"kubernetes-rhel::repo-setup",
			"kubernetes-rhel::master-setup"],
			"Deploy": ["kubernetes-rhel::master-run"]
		}
	}
},

在部署阶段,这个层的Chef运行列表是["kubernetes-rhel::flanneld", "kubernetesrhel::repo-setup", "kubernetes-rhel::master-setup"] 和["kubernetesrhel::master-run"] 。对于etcd的运行列表,我们将使用["kubernetes-rhel::etcd", "kubernetes-rhel::etcd-auth"] 执行etcd补充和认证设置。对于Kubernetes Nodes,我们将使用["kubernetesrhel::flanneld", "kubernetes-rhel::docker-engine", "kubernetesrhel::repo-setup", "kubernetes-rhel::node-setup"] 作为设置阶段的运行列表,并且使用["kubernetes-rhel::node-run"] 作为部署阶段的运行列表。

在设置完层之后,我们可以创建ELB并将它附加到栈中。实例健康检查的目标是HTTP:8080/version。它将会从80端口接收流量并转向到Master实例的6443端口:

"ELBKubMaster": {
	"DependsOn": "SecurityGroupELBKubMaster",
	"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
	"Properties": {
		"LoadBalancerName": {
			"Fn::Join": ["-",
			[{
				"Ref": "Prefix"
			},
			"Kub"]]
		},
		"Scheme": "internal",
		"Listeners": [{
			"LoadBalancerPort": "80",
			"InstancePort": "6443",
			"Protocol": "HTTP",
			"InstanceProtocol": "HTTPS"
		},
		{
			"LoadBalancerPort": "8080",
			"InstancePort": "8080",
			"Protocol": "HTTP",
			"InstanceProtocol": "HTTP"
		}],
		"HealthCheck": {
			"Target": "HTTP:8080/version",
			"HealthyThreshold": "2",
			"UnhealthyThreshold": "10",
			"Interval": "10",
			"Timeout": "5"
		},
		"Subnets": [{
			"Ref": "SubnetPrivateIdA"
		},
		{
			"Ref": "SubnetPrivateIdB"
		}],
		"SecurityGroups": [{
			"Fn::GetAtt": ["SecurityGroupELBKubMaster",
			"GroupId"]
		}]
	}
}

在创建了Master ELB之后,将它附加到OpwWorks栈上:

"OpsWorksELBAttachKubMaster": {
	"Type": "AWS::OpsWorks::ElasticLoadBalancerAttachment",
	"Properties": {
		"ElasticLoadBalancerName": {
			"Ref": "ELBKubMaster"
		},
		"LayerId": {
			"Ref": "OpsWorksLayerKubMaster"
		}
	}
}

就这样!etcd的ELB与这个设置相同,但是它的健康检查是监听HTTP:4000/version并将外界流量从80端口转到实例的4001端口。关于详细的示例,请参考我们的代码引用。在运行第二个样例模板之后,你应该能够看到OpsWorks栈、层、安全组、IAM和ELB了。如果你想到使用黙认的CloudFormation来运行,只要添加资源类型AWS::OpsWorks::Instance,指定spec,这样就都设置好了。

还可以参考

在本节,我们理解了如果编写并部署一个AWS CloudFormation模板。还请查看以下章节:

  • 第1章构建你自己的Kubernetes,架构概览这一节

  • 在AWS中构建Kubernetes基础设施

  • 使用AWS OpsWorks管理应用

  • 通过Chef recipes自动部署Kubernetes