Skip to content

Tutorial: Deploy Your First Server

This tutorial walks you through deploying a virtual machine with full networking on OpenStack, entirely from Kubernetes. By the end, you'll have a running server with a floating IP address that you can ping.

Along the way, you'll learn ORC's core concepts: managed and unmanaged resources, resource references, automatic dependency handling, and how to inspect status and conditions.

Prerequisites

  • ORC installed in your Kubernetes cluster (see Installation)
  • An OpenStack cloud with:
    • An external (provider) network
    • A flavor available for your project (e.g. m1.small)
    • An image available for your project (e.g. cirros or ubuntu)
    • Sufficient quota for a network, subnet, router, security group, port, floating IP, and server
  • kubectl configured to access your cluster

What we'll build

flowchart TB
    subgraph OpenStack
        ext[External Network]
        fip[Floating IP]
        router[Router]
        subnet[Subnet]
        net[Network]
        sg[Security Group]
        port[Port]
        server[Server]
        flavor[Flavor]
        image[Image]
    end

    ext --- router
    router --- subnet
    subnet --- net
    port --- net
    sg --- port
    port --- server
    fip --- port
    flavor -.- server
    image -.- server

We'll create all of these resources as Kubernetes objects. ORC handles the OpenStack API calls and ensures everything is created in the right order.

Step 1: Set up credentials

Create a secret containing your OpenStack clouds.yaml:

kubectl create secret generic openstack-clouds \
    --from-file=clouds.yaml=/path/to/your/clouds.yaml

Tip

You can download your clouds.yaml from the OpenStack dashboard under API AccessDownload OpenStack RC Fileclouds.yaml. For custom CA certificates and other credential options, see Set Up Cloud Credentials.

In the examples below, we use cloudName: openstack. Replace this with the name of your cloud entry in clouds.yaml if it differs.

Step 2: Import existing resources

Your OpenStack cloud already has shared resources: an external network for internet access and flavors that define VM sizes. We don't want ORC to create these; we want it to import them so other ORC resources can reference them.

This is what managementPolicy: unmanaged is for. ORC will find the resource in OpenStack and expose it as a Kubernetes object, but it won't modify or delete it.

Import the external network

First, find the name of your external network:

openstack network list --external

Now create an ORC Network object that imports it. Replace public with your external network's name:

kubectl apply --server-side -f- <<EOF
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Network
metadata:
  name: external-network
spec:
  cloudCredentialsRef:
    cloudName: openstack
    secretName: openstack-clouds
  managementPolicy: unmanaged
  import:
    filter:
      name: public
      external: true
EOF

Import a flavor

Find a flavor to use for your server:

openstack flavor list

Import it by name. Replace m1.small with a flavor available on your cloud:

kubectl apply --server-side -f- <<EOF
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Flavor
metadata:
  name: my-flavor
spec:
  cloudCredentialsRef:
    cloudName: openstack
    secretName: openstack-clouds
  managementPolicy: unmanaged
  import:
    filter:
      name: m1.small
EOF

Import an image

Import an existing image. Replace cirros with an image available on your cloud:

kubectl apply --server-side -f- <<EOF
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Image
metadata:
  name: my-image
spec:
  cloudCredentialsRef:
    cloudName: openstack
    secretName: openstack-clouds
  managementPolicy: unmanaged
  import:
    filter:
      name: cirros
EOF

Verify the imports

Check that all three resources are available:

kubectl get networks,flavors,images

Expected output:

NAME                                             ID                                     AVAILABLE   MESSAGE
network.openstack.k-orc.cloud/external-network   c81746dd-375a-4fcb-b33d-ee97a801f027   True        OpenStack resource is up to date

NAME                                     ID   AVAILABLE   MESSAGE
flavor.openstack.k-orc.cloud/my-flavor   2    True        OpenStack resource is up to date

NAME                                   ID                                     AVAILABLE   MESSAGE
image.openstack.k-orc.cloud/my-image   03046f35-421b-46cd-8634-de2fbc6254e1   True        OpenStack resource is up to date

What if an import isn't working?

If an import filter matches no resources, ORC keeps retrying: the resource stays Progressing: True with a message like "Waiting for OpenStack resource to be created externally". Double-check the filter values (names are case-sensitive).

If a filter matches multiple resources, ORC reports an error with Progressing: False. Make the filter more specific.

See Troubleshooting for more debugging steps.

Step 3: Create a network and subnet

Now we'll create managed resources: resources that ORC fully controls. It will create them in OpenStack, keep their status in sync, and delete them when we delete the Kubernetes objects.

Create a private network:

kubectl apply --server-side -f- <<EOF
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Network
metadata:
  name: my-network
spec:
  cloudCredentialsRef:
    cloudName: openstack
    secretName: openstack-clouds
  managementPolicy: managed
  resource:
    description: Tutorial network
EOF

Create a subnet on that network:

kubectl apply --server-side -f- <<EOF
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Subnet
metadata:
  name: my-subnet
spec:
  cloudCredentialsRef:
    cloudName: openstack
    secretName: openstack-clouds
  managementPolicy: managed
  resource:
    networkRef: my-network
    cidr: 192.168.1.0/24
    ipVersion: 4
EOF

Notice the networkRef: my-network field. This is a resource reference: it tells ORC that the subnet depends on my-network. ORC automatically:

  • Waits for my-network to be available before creating the subnet
  • Prevents deletion of my-network while the subnet still exists

You can apply both resources in any order; ORC handles the sequencing.

Verify:

kubectl get networks,subnets
NAME                                             ID                                     AVAILABLE   MESSAGE
network.openstack.k-orc.cloud/external-network   c81746dd-375a-4fcb-b33d-ee97a801f027   True        OpenStack resource is up to date
network.openstack.k-orc.cloud/my-network         a1b2c3d4-e5f6-7890-abcd-ef1234567890   True        OpenStack resource is up to date

NAME                                     ID                                     AVAILABLE   MESSAGE
subnet.openstack.k-orc.cloud/my-subnet   f1e2d3c4-b5a6-7890-fedc-ba0987654321   True        OpenStack resource is up to date

Step 4: Create a router

To give our network internet access, we need a router that connects our subnet to the external network:

kubectl apply --server-side -f- <<EOF
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Router
metadata:
  name: my-router
spec:
  cloudCredentialsRef:
    cloudName: openstack
    secretName: openstack-clouds
  managementPolicy: managed
  resource:
    description: Tutorial router
    externalGateways:
      - networkRef: external-network
EOF

Connect the subnet to the router using a RouterInterface:

kubectl apply --server-side -f- <<EOF
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: RouterInterface
metadata:
  name: my-router-interface
spec:
  type: Subnet
  routerRef: my-router
  subnetRef: my-subnet
EOF

Note

RouterInterface is different from other ORC resources: it has no cloudCredentialsRef or managementPolicy. It inherits credentials from the referenced router.

Wait for the router interface to be available:

kubectl get routers,routerinterfaces
NAME                                       ID                                     AVAILABLE   MESSAGE
router.openstack.k-orc.cloud/my-router     b2c3d4e5-f6a7-8901-bcde-f12345678901   True        OpenStack resource is up to date

NAME                                                        ID                                     AVAILABLE   MESSAGE
routerinterface.openstack.k-orc.cloud/my-router-interface   c3d4e5f6-a7b8-9012-cdef-123456789012   True        OpenStack resource is up to date

Step 5: Create a security group

By default, OpenStack blocks all incoming traffic. We need a security group that allows ICMP (ping) so we can verify our server is reachable:

kubectl apply --server-side -f- <<EOF
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: SecurityGroup
metadata:
  name: allow-icmp
spec:
  cloudCredentialsRef:
    cloudName: openstack
    secretName: openstack-clouds
  managementPolicy: managed
  resource:
    description: Allow ICMP traffic
    rules:
      - description: Allow ping
        direction: ingress
        protocol: icmp
        ethertype: IPv4
EOF

Step 6: Create a port and server

A port connects a server to a network. Create one with our security group:

kubectl apply --server-side -f- <<EOF
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Port
metadata:
  name: my-port
spec:
  cloudCredentialsRef:
    cloudName: openstack
    secretName: openstack-clouds
  managementPolicy: managed
  resource:
    networkRef: my-network
    securityGroupRefs:
      - allow-icmp
    addresses:
      - subnetRef: my-subnet
EOF

Now create the server:

kubectl apply --server-side -f- <<EOF
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Server
metadata:
  name: my-server
spec:
  cloudCredentialsRef:
    cloudName: openstack
    secretName: openstack-clouds
  managementPolicy: managed
  resource:
    imageRef: my-image
    flavorRef: my-flavor
    ports:
      - portRef: my-port
EOF

The server has three resource references: imageRef, flavorRef, and portRef. ORC waits for all of them to be available before asking OpenStack to create the server.

Watch the server come up:

kubectl get server my-server -w
NAME        ID                                     AVAILABLE   MESSAGE
my-server   0357c57f-51d9-401a-9b3f-ff16003708dc   False       Waiting for OpenStack resource to be ready...
my-server   0357c57f-51d9-401a-9b3f-ff16003708dc   False       Waiting for OpenStack resource to be ready...
my-server   0357c57f-51d9-401a-9b3f-ff16003708dc   True        OpenStack resource is up to date

Press Ctrl+C to stop watching.

Step 7: Assign a floating IP

To reach the server from outside the OpenStack cloud, create a floating IP and attach it to the port:

kubectl apply --server-side -f- <<EOF
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: FloatingIP
metadata:
  name: my-fip
spec:
  cloudCredentialsRef:
    cloudName: openstack
    secretName: openstack-clouds
  managementPolicy: managed
  resource:
    floatingNetworkRef: external-network
    portRef: my-port
EOF

Wait for it to be ready:

kubectl get floatingip my-fip
NAME     ID                                     AVAILABLE   ADDRESS       MESSAGE
my-fip   ccfd0a8f-b0d9-4356-a8c1-c938ca7abdd9   True        172.24.4.91   OpenStack resource is up to date

Step 8: Verify it works

The ADDRESS column in the previous step shows the floating IP that OpenStack assigned. Ping it from a host that can reach your OpenStack external network:

ping -c 3 $(kubectl get floatingip my-fip -o jsonpath='{.status.resource.floatingIP}')
PING 172.24.4.91 (172.24.4.91): 56 data bytes
64 bytes from 172.24.4.91: icmp_seq=0 ttl=63 time=2.1 ms
64 bytes from 172.24.4.91: icmp_seq=1 ttl=63 time=1.3 ms
64 bytes from 172.24.4.91: icmp_seq=2 ttl=63 time=1.5 ms

🎉 Your server is running and reachable, deployed entirely from Kubernetes!

Step 9: Inspect the deployment

Let's look at everything we created:

kubectl get openstack

This lists all ORC resources in the current namespace. You can also inspect the full observed state of any resource. For example:

kubectl get server my-server -o yaml

The .status.resource field contains the state as observed from OpenStack, including fields that OpenStack assigns (like hostID and addresses).

Cleanup

Delete all the resources we created:

kubectl delete floatingip my-fip
kubectl delete server my-server
kubectl delete port my-port
kubectl delete securitygroup allow-icmp
kubectl delete routerinterface my-router-interface
kubectl delete router my-router
kubectl delete subnet my-subnet
kubectl delete network my-network

# These are unmanaged: deleting the ORC objects does not
# delete the underlying OpenStack resources
kubectl delete flavor my-flavor
kubectl delete image my-image
kubectl delete network external-network

ORC deletes managed resources from OpenStack automatically. Dependency ordering is enforced: if you delete everything at once, ORC will delete them in the correct order (e.g., the subnet is deleted before the network).

# Delete all ORC resources at once (alternative to the above)
kubectl delete openstack --all

What you've learned

  • Managed resources (managementPolicy: managed) are fully controlled by ORC: created, updated, and deleted.
  • Unmanaged resources (managementPolicy: unmanaged) import existing OpenStack resources without modifying them.
  • Resource references (*Ref fields) create dependencies between resources. ORC handles ordering automatically.
  • Status conditions (Available and Progressing) tell you whether a resource is ready or still being worked on.
  • kubectl get openstack lists all ORC resources at once.

Tip

If you'd prefer to skip the manual YAML and use pre-built kustomize overlays instead, see Running Examples.

Next steps

  • Core Concepts: Deeper coverage of management policies, imports, and deletion behavior
  • Drift Detection: Periodically re-sync resources to detect out-of-band changes
  • CRD Reference: Full documentation of all resource types and fields
  • Troubleshooting: Diagnose and fix common issues