Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
languageyml
titleCommon Infrastructurecommon.yaml
linenumberstrue
collapsetrue
heat_template_version: 2018-08-31

description: >
  A template to create common base infrastructure for the heat-guide at 
  https://www.ntnu.no/wiki/display/skyhigh/Openstack+Heat

resources:
  network:
    type: OS::Neutron::Net
 
  subnet_v4:
    type: OS::Neutron::Subnet
    properties:
      network_id: { get_resource: network }
      cidr: '192.168.0.0/24'
      dns_nameservers: [ '129.241.0.200', '129.241.0.201' ]
      ip_version: 4 

  router:
    type: OS::Neutron::Router
    properties:
      external_gateway_info: { network: ntnu-internal } 

  router_interface_v4:
    type: OS::Neutron::RouterInterface
    properties:
      router_id: { get_resource: router }
      subnet: { get_resource: subnet_v4 }

  secgroup_generic:
    type: OS::Neutron::SecurityGroup
    properties:
      description: |
        A security group allowing users connect to the VM's using ssh
      rules:
       - protocol: icmp
         remote_ip_prefix: '0.0.0.0/0'
       - protocol: tcp
         port_range_min: 22
         port_range_max: 22
         remote_ip_prefix: '0.0.0.0/0'
       - protocol: tcp
         remote_ip_prefix: '192.168.0.0/24'
         port_range_min: 111
         port_range_max: 111
       - protocol: udp
         remote_ip_prefix: '192.168.0.0/24'
         port_range_min: 111
         port_range_max: 111
       - protocol: tcp
         remote_ip_prefix: '192.168.0.0/24'
         port_range_min: 2049
         port_range_max: 2049
       - protocol: udp
         remote_ip_prefix: '192.168.0.0/24'
         port_range_min: 2049
         port_range_max: 2049
       - protocol: tcp
         remote_ip_prefix: '192.168.0.0/24'
         port_range_min: 32767
         port_range_max: 32768
       - protocol: udp
         remote_ip_prefix: '192.168.0.0/24'
         port_range_min: 32767
         port_range_max: 32768

outputs:
  network:
    description: The network created by the template
    value: { get_resource: network }
  secgroup_generic:
    description: The security-group allowing generiv VM access.
    value: { get_resource: secgroup_generic }

...

Code Block
languageyml
titleCommon InfrastructureHeat Parameters
linenumberstrue
collapsetrue
parameters:
  flavor:
    type: string
    label: Fileserver flavor
    description: The flavor used to spawn the fileserver 
    constraints:
      - custom_constraint: nova.flavor
  ubuntu:
    type: string
    label: Fileserver image 
    description: The image used to spawn the fileserver 
    constraints:
      - custom_constraint: glance.image
  admin-ssh-key:
    type: string
    label: SSH Key admin 
    description: The SSH-key to inject in the fileserver for admin-purposes.
  user-ssh-key:
    type: string
    label: SSH Key User
    description: The SSH-key to inject in the fileserver for the user user.
  secgroup_generic:
    type: string
  network:
    type: string
  volume_type:
    type: string
    label: Volume type 
    description: The cinder-type used to create the volume for the fileserver 
    default: 'HDD-300'
    constraints:
      - custom_constraint: cinder.vtype 
  volume_size:
    type: number
    label: Volume size 
    default: 2
    description: The size of the exported volume from the fileserver 

...

  • flavor: An openstack flavor defining the specifications for the two virtual machines we are going to make
  • ubuntu: The ID of the openstack-image which we are using to create our VM's
  • admin-ssh-key: An ssh-key to be injected into the "administrator" user of the machines
  • user-ssh-key: An ssh-key to be injected into the "user" user of the machines
  • secgroup_generic: The ID of the security-group to use
  • network: The ID of the network to connect the machines to
  • volume_type: The name of the Openstack volume type to use. This one defaults to "HDD-300", and you do not need to specify it unless you want another type.
  • volume_size: The size og the volume attached to the fileserver. This one defaults to "2", an you do not need to specify it unless you want another size.

Defining the fileserver VM

Creating the fileserver VM is going to need a couple of resources:

  • A cloud-config snippet creating the users and injecting the ssh-keys
  • A cloud-config snippet installing the nfs-server packages, creating the /etc/exports file and formatting the extra disk.
  • A shell-script altering the NFS-server configuration, and mounting the extra disk.
  • A floating IP
  • A virtual server
  • A volume

The configuration-snippets and shellscript can be defined as three seperate resources, and then joined together to a single resource which can be given to a VM like so:

Code Block
languageyml
titleCloud-conifg and scripts
linenumberstrue
collapsetrue
  cloudconf_fileserver:
    type: OS::Heat::MultipartMime
    properties:
      parts:
      - config: {get_resource: cloudconf_base}
      - config: {get_resource: cloudconf_fileservers}
      - config: {get_resource: script_fileserver}

  cloudconf_base:
    type: OS::Heat::CloudConfig
    properties:
      cloud_config:
        package_update: true
        package_upgrade: true
        timezone: "Europe/Oslo"
        users:
         - name: administrator
           sudo: ALL=(ALL) NOPASSWD:ALL
           lock_passwd: True
           shell: /bin/bash
           ssh_authorized_keys:
            - { get_param: admin-ssh-key }
         - name: user
           lock_passwd: True
           shell: /bin/bash
           ssh_authorized_keys:
            - { get_param: user-ssh-key }
        power_state:
          mode: 'reboot'
          message: 'Reboots after installing'
          condition: True

  cloudconf_fileservers:
    type: OS::Heat::CloudConfig
    properties:
      cloud_config:
        packages:
         - 'nfs-kernel-server'
         - 'pwgen'
        write_files:
         - content: '/opt/data/shared 192.168.0.0/24(rw,sync,no_subtree_check)'
           path: '/etc/exports'
         - content: |
             options lockd nlm_udpport=32768 nlm_tcpport=32768
             options nfs callback_tcpport=32764
           path: '/etc/modprobe.d/local.conf'
        disk_setup:
          /dev/vdb:
            table_type: gpt
            layout: true
            overwrite: false
        fs_setup:
         - filesystem: 'ext4'
           label: 'datapartition'
           device: '/dev/vdb'
           partition: 'auto'

  script_fileserver:
    type: OS::Heat::SoftwareConfig
    properties:
      group: ungrouped
      config: |  
        #!/bin/bash
        # Restrict NFS ports
        sed -i -r 's/STATDOPTS=.*/STATDOPTS="--port 32765 --outgoing-port 32766"/' /etc/default/nfs-common
        sed -i -r 's/RPCMOUNTDOPTS=.*/RPCMOUNTDOPTS="-p 32767"/' /etc/default/nfs-kernel-server 
        # Mount disks
        echo "/dev/vdb1	/opt/data	ext4	defaults,comment=cloudconfig	0	0" >> /etc/fstab
        mkdir /opt/data
        mount /dev/vdb1 /opt/data
        mkdir /opt/data/shared
        chown user:user /opt/data/shared

The next part is to create the fileserver, and the floating IP to attach to it. For some reason Heat does not let us add floating IP's to servers, but we are allowed to add it to ports which can be attached to the servers. So, creating a server with a floating IP requires three resources: a server, an IP and an IP attachment. The server also uses the "cloudconf_fileserver" resource created above, and it gets a custom hostname set which is "STACK-fileserver" where we replace "STACK" with the name of the stack when it is created. The port we attach to the server gets both an floating IP and the security-group.

Code Block
languageyml
titleFileserver with floating IP
linenumberstrue
collapsetrue
  fileserver:
    type: OS::Nova::Server
    properties:
      name:
        str_replace:
          template: 'STACK-fileserver'
          params:
            STACK: { get_param: OS::stack_name } 
      image: { get_param: ubuntu }
      flavor: { get_param: flavor }
      networks:
       - {"port": { get_resource: fileserver_port }}
      user_data_format: RAW
      user_data: { get_resource: cloudconf_fileserver } 

  fileserver_port:
    type: OS::Neutron::Port
    properties:
      admin_state_up: true
      network_id: { get_param: network }
      security_groups: [{ get_param: secgroup_generic }]

  fileserver_floatingip:
    type: OS::Neutron::FloatingIP
    properties:
      floating_network: 'ntnu-internal'
      port_id: { get_resource: fileserver_port }

The only bits left now is creating a volume and attaching to the server:

Code Block
languageyml
titleFileserver with floating IP
linenumberstrue
collapsetrue
  volume:
    type: OS::Cinder::Volume
    properties:
      size: { get_param: volume_size }
      volume_type: { get_param: volume_type }
  volume_attach:
    type: OS::Cinder::VolumeAttachment
    properties:
      instance_uuid: { get_resource: fileserver }
      volume_id: { get_resource: volume }

Defining the client VM

In addition to the server we want to have a client. This client uses one of the cloudconfig-snippets from the server (the one creating the users) in addition to a cloudconfig-snippet instructing the server to mount the volume. The client can be deined like so:

Code Block
languageyml
titleFileserver with floating IP
linenumberstrue
collapsetrue
  cloudconf_nfsmount:
    type: OS::Heat::CloudConfig
    properties:
      cloud_config:
        packages:
         - 'nfs-common'
        write_files:
         - content: 
             str_replace:
               template: 'IP:/opt/data/shared	/mnt/filserver	nfs4	defaults	0 0'
               params:
                 IP: { get_attr: [ fileserver, networks, {get_param: network}, 0 ] } 
           path: '/etc/fstab'
           append: true

  cloudconf_client:
    type: OS::Heat::MultipartMime
    properties:
      parts:
      - config: {get_resource: cloudconf_base}
      - config: {get_resource: cloudconf_nfsmount}
        
  nfsclient:
    type: OS::Nova::Server
    properties:
      name:
        str_replace:
          template: 'STACK-client'
          params:
            STACK: { get_param: OS::stack_name } 
      image: { get_param: ubuntu }
      flavor: { get_param: flavor }
      networks:
       - {"port": { get_resource: nfsclient_port }}
      user_data_format: RAW
      user_data: { get_resource: cloudconf_client } 

  nfsclient_port:
    type: OS::Neutron::Port
    properties:
      admin_state_up: true
      network_id: { get_param: network }
      security_groups: [{ get_param: secgroup_generic }]

  nfsclient_floatingip:
    type: OS::Neutron::FloatingIP
    properties:
      floating_network: 'ntnu-internal'
      port_id: { get_resource: nfsclient_port }

Stitching it all together

Adding all the components for the client/server VM's together in a single file, and adding some outputs letting us easily see the IP-addresses assigned to the VM's we get a Heat-Template looking like so:

Code Block
languageyml
titleclientserver-lab.yaml
linenumberstrue
collapsetrue
heat_template_version: 2018-08-31

description: >
  This template creates, installs and configures a fileserver, serving as the
  file-repository for a certain NICE2 project.

parameters:
  flavor:
    type: string
    label: Fileserver flavor
    description: The flavor used to spawn the fileserver 
    constraints:
      - custom_constraint: nova.flavor
  ubuntu:
    type: string
    label: Fileserver image 
    description: The image used to spawn the fileserver 
    constraints:
      - custom_constraint: glance.image
  admin-ssh-key:
    type: string
    label: SSH Key admin 
    description: The SSH-key to inject in the fileserver for admin-purposes.
  user-ssh-key:
    type: string
    label: SSH Key User
    description: The SSH-key to inject in the fileserver for the user user.
  secgroup_generic:
    type: string
  network:
    type: string
  volume_type:
    type: string
    label: Volume type 
    description: The cinder-type used to create the volume for the fileserver 
    default: 'HDD-300'
    constraints:
      - custom_constraint: cinder.vtype 
  volume_size:
    type: number
    label: Volume size 
    default: 2
    description: The size of the exported volume from the fileserver 

resources:
  fileserver:
    type: OS::Nova::Server
    properties:
      name:
        str_replace:
          template: 'STACK-fileserver'
          params:
            STACK: { get_param: OS::stack_name } 
      image: { get_param: ubuntu }
      flavor: { get_param: flavor }
      networks:
       - {"port": { get_resource: fileserver_port }}
      user_data_format: RAW
      user_data: { get_resource: cloudconf_fileserver } 

  fileserver_port:
    type: OS::Neutron::Port
    properties:
      admin_state_up: true
      network_id: { get_param: network }
      security_groups: [{ get_param: secgroup_generic }]

  fileserver_floatingip:
    type: OS::Neutron::FloatingIP
    properties:
      floating_network: 'ntnu-internal'
      port_id: { get_resource: fileserver_port }

  volume:
    type: OS::Cinder::Volume
    properties:
      size: { get_param: volume_size }
      volume_type: { get_param: volume_type }

  volume_attach:
    type: OS::Cinder::VolumeAttachment
    properties:
      instance_uuid: { get_resource: fileserver }
      volume_id: { get_resource: volume }

  cloudconf_fileserver:
    type: OS::Heat::MultipartMime
    properties:
      parts:
      - config: {get_resource: cloudconf_base}
      - config: {get_resource: cloudconf_fileservers}
      - config: {get_resource: script_fileserver}

  cloudconf_base:
    type: OS::Heat::CloudConfig
    properties:
      cloud_config:
        package_update: true
        package_upgrade: true
        timezone: "Europe/Oslo"
        users:
         - name: administrator
           sudo: ALL=(ALL) NOPASSWD:ALL
           lock_passwd: True
           shell: /bin/bash
           ssh_authorized_keys:
            - { get_param: admin-ssh-key }
         - name: user
           lock_passwd: True
           shell: /bin/bash
           ssh_authorized_keys:
            - { get_param: user-ssh-key }
        power_state:
          mode: 'reboot'
          message: 'Reboots after installing'
          condition: True

  cloudconf_fileservers:
    type: OS::Heat::CloudConfig
    properties:
      cloud_config:
        packages:
         - 'nfs-kernel-server'
         - 'pwgen'
        write_files:
         - content: '/opt/data/shared 192.168.0.0/24(rw,sync,no_subtree_check)'
           path: '/etc/exports'
         - content: |
             options lockd nlm_udpport=32768 nlm_tcpport=32768
             options nfs callback_tcpport=32764
           path: '/etc/modprobe.d/local.conf'
        disk_setup:
          /dev/vdb:
            table_type: gpt
            layout: true
            overwrite: false
        fs_setup:
         - filesystem: 'ext4'
           label: 'datapartition'
           device: '/dev/vdb'
           partition: 'auto'

  script_fileserver:
    type: OS::Heat::SoftwareConfig
    properties:
      group: ungrouped
      config: |  
        #!/bin/bash
        # Restrict NFS ports
        sed -i -r 's/STATDOPTS=.*/STATDOPTS="--port 32765 --outgoing-port 32766"/' /etc/default/nfs-common
        sed -i -r 's/RPCMOUNTDOPTS=.*/RPCMOUNTDOPTS="-p 32767"/' /etc/default/nfs-kernel-server 
        # Mount disks
        echo "/dev/vdb1	/opt/data	ext4	defaults,comment=cloudconfig	0	0" >> /etc/fstab
        mkdir /opt/data
        mount /dev/vdb1 /opt/data
        mkdir /opt/data/shared
        chown user:user /opt/data/shared

  cloudconf_nfsmount:
    type: OS::Heat::CloudConfig
    properties:
      cloud_config:
        packages:
         - 'nfs-common'
        write_files:
         - content: 
             str_replace:
               template: 'IP:/opt/data/shared	/mnt/filserver	nfs4	defaults	0 0'
               params:
                 IP: { get_attr: [ fileserver, networks, {get_param: network}, 0 ] } 
           path: '/etc/fstab'
           append: true

  cloudconf_client:
    type: OS::Heat::MultipartMime
    properties:
      parts:
      - config: {get_resource: cloudconf_base}
      - config: {get_resource: cloudconf_nfsmount}
        
  nfsclient:
    type: OS::Nova::Server
    properties:
      name:
        str_replace:
          template: 'STACK-client'
          params:
            STACK: { get_param: OS::stack_name } 
      image: { get_param: ubuntu }
      flavor: { get_param: flavor }
      networks:
       - {"port": { get_resource: nfsclient_port }}
      user_data_format: RAW
      user_data: { get_resource: cloudconf_client } 

  nfsclient_port:
    type: OS::Neutron::Port
    properties:
      admin_state_up: true
      network_id: { get_param: network }
      security_groups: [{ get_param: secgroup_generic }]

  nfsclient_floatingip:
    type: OS::Neutron::FloatingIP
    properties:
      floating_network: 'ntnu-internal'
      port_id: { get_resource: nfsclient_port }

outputs:
  fileserver_address:
    description: Fileserver address
    value: { get_attr: [ fileserver_floatingip, fixed_ip_address ] }
  fileserver_floating_address:
    description: Fileserver floating IP address
    value: { get_attr: [ fileserver_floatingip, floating_ip_address ] }
  client_address:
    description: Client address
    value: { get_attr: [ nfsclient_floatingip, fixed_ip_address ] }
  client_floating_address:
    description: Client floating IP address
    value: { get_attr: [ nfsclient_floatingip, floating_ip_address ] }