Im Zuge einer automatisierten Installation von RedHat OpenShift auf OpenStack wollte ich das natürlich möglichst automatisiert ausführen.

Dazu bringt OpenStack die Komponente Heat mit, mit der es möglich ist einen ganzen Stack auf einmal automatisiert auszurollen, zu ändern, zu skalieren und zu löschen.

Heat kann mittels Template, das wiederum HOT(=Heat Orchestration Template) heißt, gefüttert werden.

Die Notationssprache in diesem Template ist YAML(=YAML Ain’t Markup Language).

 

Das Regelwerk für YAML ist grundsätzlich wie folgt aufgebaut:

- Einrücken über Leerzeichen bestimmt die Struktur und die Beziehung der einzelnen Elemente zueinander. Elemente die mehr eingerückt sind, sind Unterlemente des vorherigen Elements, dass nicht so weit eingerückt ist bzw. nicht so viele Leerzeichen davor hat.

- Listenmitglieder werden über einen Bindestrich definiert, d.h. alle weiter untergeordneten Elemente, die auf der gleichen Ebene oder weiter eingerückt sind, gehören dann zu dieser Liste.

- Zusammenhängende Daten, werden über einen Schlüssel, direkt gefolgt von einem Doppelpunkt(:), Leerzeichen und dem dazugehörigen Wert definiert. Im Prinzip handelt es sich um eine Variableninitialisierung.

- Zusammenhängende Textblöcke werden über eine Pipe(|) gekennzeichnet, d.h. auch hier dass alle weiter untergeordneten Elemente, die auf der gleichen Ebene oder weiter eingerückt sind, zu diesem Textblock gehören.

- Kommentare werden von einem # angeführt.

Weiterführende Informationen auf Englisch finden sich unter folgendem Link:

https://www.digitalocean.com/community/tutorials/an-introduction-to-cloud-config-scripting

Bevor wir beginnen möchte ich darauf Hinweisen einen vernünftigen Editor dafür zu benutzen, der über ein entsprechendes Highlighting verfügt.

Ich habe bisher Notepad++ benutzt, aber evtl. gibt es noch bessere.

 

Kommen wir nun zum Template. Der Aufbau ist unter dem folgenden Link beschrieben:

http://docs.openstack.org/developer/heat/template_guide/index.html

Der Grundaufbau eines Template ist hier beschrieben:

http://docs.openstack.org/developer/heat/template_guide/hot_spec.html

Ressourcentypen sind unter folgendem Link zu finden:

http://docs.openstack.org/developer/heat/template_guide/openstack.html

Die Grundstruktur sieht wie folgt aus:


heat_template_version: 2016-10-14

  # Bestimmt die Version des Templates und dem entsprechenden Featureset und spiegelt bei Neuerstellung die Version des verwendeten OpenStack wieder. Allerdings kann man hier auch die Strategie fahren, dass man ein OpenStack übergreifendes Template erstellen möchte, dass mit möglichst vielen Umgebungen unterschiedlicher Versionen zusammenarbeitet.

 

description:

  # Eine einfache Beschreibung des Templates

 

parameter_groups:

  # Deklaration von Parameter Gruppen

 

parameters:

  # Deklaration von Eingabeparametern, die im weiteren Verlauf des Templates verwendet werden können. Im jeweiligen Element kann dieser Wert mittels {get_param: <Variablenname> } abgefragt werden. Verschiedene Typen sind möglich: <string | number | json | comma_delimited_list | boolean> und können mit konkreten Werten vorbelegt werden.

Diese hier erstellten Werte werden bei Erstellung des Stacks in der WebGUI angezeigt und können entsprechend verändert werden. Ist kein default Wert vorhanden, so muss ein Wert bei Erstellung mitgegeben werden. Weiteres unter obigem Link.

 

resources: 

  # Hier werden die eigentlich zu erstellenden Ressourcen festgelegt. Welche Typen hier erstellt werden können ist hier zu finden: http://docs.openstack.org/developer/heat/template_guide/openstack.html

Welche Resource Types aber wirklich zur Verfügung stehen hängt a) von den Rechten des Benutzers ab und b) von der verwendeten OpenStack Version. Unter jedem Ressourcentyp steht die OpenStack Version ab wann sie verfügbar ist.

 

outputs:

  # Hier können Ausgaben definiert werden. Beispielsweise möchte man sich ausgeben lassen, welche IP nun ein Server erhalten hat, da diese im Erstellvorgang automatisch vergeben wird. Das geht bspw. mit dem Befehl {get_attr: [<ressource>, name] } . Wobei "name" ein Attribut des Objekts ist, dass sich hinter der abgefragten Ressource befindet. Welche Attribute abgefragt werden können ist im jeweiligen "Resource Type" festgelegt. Siehe obigen Link.

 

conditions:

  # Hier können Bedingungen definiert werden.


 

Das sind erst einmal genug Informationen um mit einem Template zu beginnen. Folgend sind zwei Templates gelistet, die ein Netzwerk und einen Server erstellen.

Die Templates wurden mit den Informationen aus dem folgenden PDF erstellt:

https://access.redhat.com/sites/default/files/attachments/ocp-on-osp.pdf

 

Voraussetzung:

- Ein bestehendes Public Netz innerhalb OpenStack mit dem Namen "floating" bzw. diese Stelle muss dann angepasst werden.

Weitere Bemerkung:

- Ich habe hier für das Beispiel nur ein Netzwerk angelegt und den Bastion Host von dem aus OpenShift weiter installiert wird. Es fehlen hier noch weitere SecurityGroups für die Master-,Infra- und Nodeserver.

- Alle anzupassenden Punkte sind mit "Todo:" gekennzeichnet. Der entsprechende Kommentar sollte dann auch entfernt werden.

Netzwerkerstellung:


heat_template_version: 2015-10-15 #Definition der OpenStack Version, die unterstützt werden soll.
description: "OpenShift Template for the networking component. It creates a network, subnet inside the network, gateway and security group"
parameters:
  #--------------------Definition of Security Groups---------------------
  secGroupBasicBastion: #Namensvergabe für den Parameter. Hierauf wird intern referenziert mit bspw. { get_param: secGroupBasicBastion }.
    type: string
    description: Security Group for Bastion Hosts
    default: BasicBastion #Default Wert, der bei Erstellung über die WebGui im Eingabefenster angezeigt wird.

  #--------------------Definition of network names----------------------
  private_net_name:
    type: string
    description: Name of private network to be created
    default: OpenShiftSharedNetwork
  private_subnet_name:
    type: string
    description: Name of private network to be created
    default: OpenShiftSharedNetworkSubnet
  public_net:
    type: string
    description: ID or name of public network for which floating IP addresses will be allocated
    default: floating #Todo: Das Public Netz muss bereits existieren bzw. im Template oder während der Erstellung geändert werden.

  #--------------------Definition of network Range-----------------------
  private_net_cidr:
    type: string
    description: Private network address (CIDR notation)
    default: 172.17.0.0/16 #Todo: Netzwerkrange bei Bedarf ändern
  all_net_cidr:
    type: string
    description: Private network address (CIDR notation)
    default: 0.0.0.0/0 #Das ist der default Wert, der bei Erstellung  des Stacks geändert werden kann. allerdings ist damit auch nur ein Ziel oder eine Quelle im Internet gemeint und wird primär in den Security Groups als Quelle bzw. Ziel benötigt.

resources: #Ab diesem Eintrag werden nun die tatsächlichen Objekte definiert und erstellt. Man kann nun die Parameter aus dem obigen Bereich nehmen oder Werte hartcoden.
  #------------------------Creation of Network and Subnet----------------
  private_net: #Ebenfalls wieder eine Namensvergabe der Ressource um darauf intern verweisen zu können bspw. { get_resource: private_net }
    type: OS::Neutron::Net #Der Typ des Objektes das angelegt werden soll. Mithilfe der Dokumentation werden die gewünschten Properties definiert: OS::Neutron::Net
    properties:
      name: { get_param: private_net_name }
  private_subnet:
    type: OS::Neutron::Subnet
    properties:
      name: { get_param: private_subnet_name }
      dns_nameservers: [192.168.17.1,192.168.17.2]    #Todo: DNS Server müssen hier angepasst werden.
      network_id: { get_resource: private_net }
      cidr: { get_param: private_net_cidr }

  #------------------------Creation of Gateway---------------------------
  router: #Beschreibung des Gateway: Es wird ein Router erstellt, dessen externe Schnittstelle sich im öffentlichen Netz befindet.
    type: OS::Neutron::Router
    properties:
      external_gateway_info:
        network: { get_param: public_net }
  router_interface: #Das Interface des Gateway ist ersteinmal unabhängig vom Gateway selbst. Es wird separat erstellt und an den Router und in das Subnet gehangen.
    type: OS::Neutron::RouterInterface
    properties:
      router_id: { get_resource: router }
      subnet_id: { get_resource: private_subnet }

  #------------------------Creation of Security Group---------------------------

  secGroupBasicBastion_res: #Hier wird eine Security Group mit eingehendem SSH erstellt.
    type: OS::Neutron::SecurityGroup
    properties:
      description: Security Group for Bastion Host
      name: { get_param: secGroupBasicBastion }
      rules: [{"direction": ingress, "port_range_min": 22, "port_range_max": 22, "remote_mode": remote_ip_prefix, "protocol": tcp, "remote_ip_prefix": { get_param: all_net_cidr }}]


Zweites Template zur Erstellung eines Hosts:


heat_template_version: 2015-10-15
description: "OpenShift Template for the Bastion host Component"
parameters:
  #--------------------Definition of instance Name-----------------------
  nodenameBastion:
    type: string
    description: Name of Bastion
    default: Servername.alleserver.com #Todo: Servername sollte geändert werden.
  #----------------------------------------------------------------------
  #--------------------Definition of image and flavor sizes--------------
  image:
    type: string
    description: Name of image to use for servers
    default: "Red Hat Enterprise Linux 7.2" #das zu verwendende OS wird hier definiert.
  flavor:
    type: string
    description: Flavor to use for servers
    default: ocp.bastion #die zu verwendende Größe des Servers wird hier definiert.
  #----------------------------------------------------------------------
  #--------------------Definition of availability Zones------------------
  availabilityZone1:
    type: string
    description: Availability Zone DC1
    default: dc1
  #----------------------------------------------------------------------
  #--------------------Definition of Security Groups---------------------
  secGroupBasicBastion:
    type: string
    description: Security Group for Bastion Hosts
    default: BasicBastion
  #----------------------------------------------------------------------
  #--------------------Definition of Networks----------------------------
  private_net_name:
    type: string
    description: Name of private network to be created
    default: OpenShiftSharedNetwork
  private_subnet_name:
    type: string
    description: Name of private network to be created
    default: OpenShiftSharedNetworkSubnet
  public_net:
    type: string
    description: ID or name of public network for which floating IP addresses will be allocated
    default: floating
  #----------------------------------------------------------------------

resources:
  #--------------------Creation of Instances-----------------------------
  #
  ServerBastion:
    type: OS::Nova::Server
    properties:
      name: {get_param: nodenameBastion}
      image: { get_param: image }
      flavor: { get_param: flavor }
      key_name: OpenShiftsharedKP #Todo: Ändern !Der Private Key sollte innerhalb OpenStack vorher erstellt sein. Ich habe ihn hier hardcoded, sollte aber als Parameter auftauchen.Mehr Arbeit für Dich !
      availability_zone: { get_param: availabilityZone1}
      networks:
        - port: { get_resource: Bastion_port } #Hier wieder das Gleiche wie beim Gateway. Interface und Serverobjekt sind voneinander getrennt.
      user_data_format: RAW
      user_data: #User_data ist ein sehr interessanter Teil und wird von mir in einem anderen Artikel behandelt werden. Grundsätzlich wird hier alles verarbeitet was innerhalb des OS stattfinden soll. Ein Beispiel ist unten aufgeführt.
        str_replace: #Das ist eine Funktion, die im Bereich params definiert hat nach welchem Text sie im Bereich template ersetzen soll. In diesem Fall wird der Parameter $shortname vorbelegt mit dem Wert des Bastion Hostnames, ein Split über das Zeichen . durchgeführt und aus dem Ergebnis wird das erste Element genommen. Danach wird das Ergebnis im Bereich template das Wort "$shortname" ersetzen.
          template: |
             #!/bin/bash -ex
             echo "Running boot script"
             /usr/sbin/subscription-manager register --name=$shortname #Hier weitere Optionen einfügen, die für Sie passen !
             /usr/sbin/subscription-manager repos --disable=*
             /usr/sbin/subscription-manager repos --enable=rhel-7-server-rpms --enable=rhel-7-server-extras-rpms --enable=rhel-7-server-ose-3.3-rpms
             yum install -y atomic-openshift-utils
          params:
            $shortname: {str_split: ['.', { get_param: nodenameBastion }, 0]}
  #---------------------Instance private Network Interface---------------
  Bastion_port:
    type: OS::Neutron::Port
    properties:
      network_id: { get_param: private_net_name }
      fixed_ips:
        - subnet_id: { get_param: private_subnet_name }
      security_groups:
        - { get_param: secGroupBasicBastion}
  #---------------------Instance public Network--------------------------
  Bastion_public:
    type: OS::Neutron::FloatingIP
    properties:
      floating_network: { get_param: public_net }
      port_id: { get_resource: Bastion_port }
  #----------------------------------------------------------------------
outputs: #Hier werden Werte entsprechend ausgegeben.
  BastionName:
    description: Name
    value: {get_attr: [ServerBastion, name] } #Der Name des Servers wird ausgegeben. get-attr Wie geschrieben muss man sich die möglichen Attribute aus der Objekt Dokumentation holen. Für dieses Objekt OS::Nova::Server existiert das Attribut "Name".
  BastionPrivateIP:
    description: Private IP Address
    value: {get_attr: [ServerBastion, networks, {get_param: private_net_name},0] }
  BastionPublicIP:
    description: Public IP Address
    value: {get_attr: [Bastion_public, floating_ip_address]}
#  Bastion_long:
#    description: Long output of server
#    value: {get_attr: [ServerBastion]}