Um einen Eindruck von den Möglichkeiten zum Scripting mit Heat zu bekommen, habe ich etwas experimentiert und in den folgenden Beiträgen möchte ich das etwas dokumentieren. Da es meine ersten Versuche in diese Richtung sind, muss es nicht zwangsläufig alles ganz richtig oder vollständig sein, aber vielleicht ermöglicht es auch anderen einen ersten unkomplizierteren Einstieg in OpenStack und Heat.
Hier soll das folgende kleine Beispiel als flexibles Skript aufgebaut werden:
Es gibt einen Router, drei Server ein Netz. Die Server sollen in einem RFC1918-Netz liegen und so gibt es ein Subnet, das jedoch für bestimmte Dienste über NAT auch von außen zugänglich sein soll. Insgesamt werden in diesem Beispiel für die Switch vier Ports benötigt.
Die Heat-Komponente unterstützt die Formate JSON und YAML. Hier soll für die Beispiele YAML eingesetzt werden.
Die Beschreibung von YAML findet sich bei Wikipedia, einen Syntax-Checker für YAML findet sich z.B. unter http://yamllint.com/
Als Syntax kann CFN von Amazon eingesetzt werden oder hier im Beispiel HOT Heat Orchestration Template. Für die Übertragung von CFN-Skripten gibt es eine Umsetzungtabelle unter https://wiki.openstack.org/wiki/Heat/AWS-to-OpenStack-resource-mapping-in-templates
Skripte in Heat unterteilen sich in verschiedenen Bereiche
- heat_template_version: 2013-05-23 (zur Deklaration eines HOT-Templates)
- description (für die Beschreibung des Templates an sich)
- parameter-groups (für Gruppen von Parametern) (funktioniert noch nicht, anscheinend erst ab dem JUNO-release von OpenStack)
- parameters (zur Abfrage von Angaben beim Nutzer)
- resources (zur Deklaration der eigentlichen Ressourcen der Umgebung) (MANDATORY)
- outputs (zur Vereinbarung von Ausgaben bei erfolgreicher Durchführung des Skripts)
Für den wichtigsten Resource-Bereich gibt es eine vollständige Dokumentation unter http://docs.openstack.org/developer/heat/template_guide/openstack.html
In YAML werden Hierarchien durch Einrückungen ausgedrückt, wie viele Leerzeichen eingefügt werden spielt dabei keine Rolle, solange es einheitlich ist. Bei mir im Editor hat es mit „Tabs“ nicht so gut funktioniert. Ein einfaches Beispiel könnte so aussehen:
parameters: dns_server1: type: string default: 141.20.1.3 description: DNS 1 for this stack
Hier ergibt sich durch die Einrückungen die Hierarchie -> Parameters -> dns_server1 -> type:, default:, description:.
„Parameters“ markiert den Bereich des Skripts, dns_server1: definiert ein Objekt mit den Parametern type:, default: und description: .
Der Teil „parameter-groups“ dient der Sortierung von Parametern, die sonst in beliebiger Reihenfolge angezeigt werden. Im ICEHOUSE-Release scheint dieses Feature noch nicht vollständig unterstütz zu sein. Für den JUNO-Release soll sich die Unterstützung jedoch verbessern. Die Sortierungen haben die Form:
parameter_groups: - label: <human-readable label of parameter group> description: <description of the parameter group> parameters: - <param name> - <param name>
Für dieses Skript könnte eine Sortierung wie folgt aussehen:
parameter_groups: - label: IP Settings description: Groups all the IPv4 settings parameters: - cidr_net - ip_start - ip_end - gateway_address - dns_server1 - dns_server2 - label: Open Stack Settings description: All settings about the OpenStack installation parameters: - keypair_name - image_id - external_net_id - machine_flavor
Die Parameter an sich werden im nächsten Bereich „parameters“ erst definiert und können dann in den „parameter-groups“ per Namen referenziert werden.
Als Parameter sollen in dem Skript die wichtigsten Angaben vom Anwender abgefragt werden, der restliche Aufbau soll automatisch geschehen.
Die Möglichkeiten für „parameters“ sind:
parameters: <param name>: type: <string | number | json | comma_delimited_list | boolean> label: <human-readable name of the parameter> description: <description of the parameter> default: <default value for parameter> hidden: <true | false> constraints: <parameter constraints>
Die folgenden Parameter werden abgefragt:
parameters: cidr_net: type: string default: 192.168.0.0/24 description: Size of the ip-net dns_server1: type: string default: 141.20.1.3 description: DNS 1 for this stack dns_server2: type: string default: 141.20.1.31 description: DNS 2 for this stack ip_start: type: string default: 192.168.0.100 description: dhcp start address for this stack ip_end: type: string default: 192.168.0.200 description: dhcp end address for this stack gateway_address: type: string default: 192.168.0.1 description: ip address of the gateway keypair_name: type: string default: MyNewKeys description: Which keypair should be used image_id: type: string default: YOUR_IMAGE_ID_HERE description: Put your favorite image ID here external_net_id: type: string default: YOUR_EXT_NET_ID_HERE description: Ask your administrator for the external_network_id machine_flavor: type: string default: m1.small description: Your machine flavor to be used for this server. constraints: - allowed_values: [m1.tiny, m1.small, m1.large] description: Value must be one of 'm1.tiny', 'm1.small' or 'm1.large'
Fügt man dieses Skript über das Web-Interface über Project -> Orchestration -> Stacks -> Launch Stacks -> Template Source: Direct Input
ein, validiert es noch nicht, da nur „parameters“ als Bereich eingesetzt wurde und ein „resources“-Bereich fehlt, der verpflichtend ist..
(Der Bereich „Environment Source“ beim Anlegen eines Templates im Web-Interface kann für weitere Voreinstellungen genutzt werden, die in mehreren Skripten eingesetzt werden sollen.)
Die ersten Ressourcen werden ein „network“ und ein „subnet“ sein. Die Möglichkeiten hier für das „network“ sind:
type: OS::Neutron::Net properties: admin_state_up: Boolean dhcp_agent_ids: [Value, Value, ...] name: String shared: Boolean value_specs: {...}
und für das „subnet“
type: OS::Neutron::Subnet properties: allocation_pools: [{"start": String, "end": String}, {"start": String, "end": String}, ...] cidr: String dns_nameservers: [Value, Value, ...] enable_dhcp: Boolean gateway_ip: String host_routes: [{"nexthop": String, "destination": String}, {"nexthop": String, "destination": String}, ...] ip_version: Integer name: String network_id: String tenant_id: String value_specs: {...}
Wenn es nur die nötigsten Einträge sein sollen wird dies zu:
heat_template_version: 2013-05-23
description: My First Heat Template
resources: my_first_network: type: OS::Neutron::Net properties: name: My1Net
my_first_subnet: type: OS::Neutron::Subnet properties: allocation_pools: [ { start: 192.168.0.100, end: 192.168.0.200 } ] cidr: 192.168.0.0/24 dns_nameservers: [ 141.20.1.3, 141.20.1.31 ] enable_dhcp: true gateway_ip: 192.168.0.1 network_id: { get_resource: my_first_network } name: My1Subnet
Die meisten Angaben sind hier selbsterklärend. In der Zeile network_id: { get_resource: my_first_network } wird über den Befehl „get_resource:“ anstelle einer sonst anzugebenden eindeutigen ID (Objekt-UUID) als Zahl, die ID des Objekts „my_first_network“ dynamisch ersetzt. (Solche IDs sehen bei OpenStack als Zahl überlichweise aus wie z. B. „5f32fcf1-fbaf-47bf-9d39-9effbe750b97″)
Um die in der „parameters“-Sektion abgefragten Werte einzusetzen, wird über „get_param“ der Wert abgefragt und so könnte die Vereinbarung der DHCP-IP-Bereiche so aussehen:
allocation_pools: [ { start: { get_param: ip_start }, end: { get_param: ip_end } } ]
Das Skript oben läuft schon erfolgreich durch, wenn im folgenden Dialog noch der Name für den Stack vergeben wird und das Schlüsselpaar und eigene Passwort eingetragen wird. Die Option „Rollback On Failure:“ ist hilfreich, um im Fehlerfalle nicht alle bis zum Fehler angelegten Komponenten mühsam einzeln wieder löschen zu müssen, erschwert aber auch das Debuggen, da ein fehlerhafter Stack sofort wieder rückabgewickelt (gelöscht) wird.
Der erzeugte Stack sieht dann z.B. folgendermaßen aus: