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.
cirrosorubuntu) - Sufficient quota for a network, subnet, router, security group, port, floating IP, and server
kubectlconfigured 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 Access → Download OpenStack RC File → clouds.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-networkto be available before creating the subnet - Prevents deletion of
my-networkwhile 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 (
*Reffields) create dependencies between resources. ORC handles ordering automatically. - Status conditions (
AvailableandProgressing) tell you whether a resource is ready or still being worked on. kubectl get openstacklists 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