Exercise - Host a new database using Azure Cosmos DB
Now that we've reviewed the basic concepts of external states and how to deal with them using Kubernetes, let's create the resources that support your freight company's application and then create the application itself.
Create a resource group
Important
You need your own Azure subscription to complete this exercise, and you might incur charges. If you don't already have an Azure subscription, create a free account before you begin.
Sign into Azure portal using your own subscription.
Open the Cloud Shell and select Bash.
Create an Azure resource group using the
az group createcommand and specify a region. This example creates a resource group named rg-ship-manager in the eastus region:az group create --name rg-ship-manager --location eastusThe creation process can take a few moments to complete.
Create the state
As we described earlier, it's possible but not recommended to handle state in Kubernetes. Managing a highly available application state can become too difficult when you need to manage the state yourself.
To solve that problem, we externalize the state to an application that specializes in dealing with external state: Azure Cosmos DB.
Note
Although we're creating an Azure Cosmos DB instance as part of the required resources to run the application, Azure Kubernetes Service (AKS) and Azure Cosmos DB aren't related to each other.
For Azure Cosmos DB, verify that the resource provider
Microsoft.DocumentDBis registered in your subscription.az provider show --namespace Microsoft.DocumentDB --query "registrationState"If the output is
NotRegistered, register the resource provider.az provider register --namespace Microsoft.DocumentDBCreate Bash variables to store the Azure Cosmos DB account name and the resource group name for use throughout the rest of the module.
export RESOURCE_GROUP=rg-ship-manager export COSMOSDB_ACCOUNT_NAME=contoso-ship-manager-$RANDOMCreate a new Azure Cosmos DB account using the
az cosmosdb createcommand.az cosmosdb create \ --name $COSMOSDB_ACCOUNT_NAME \ --resource-group $RESOURCE_GROUP \ --kind MongoDBThe creation process can take a few moments to complete.
Create a new database using the
az cosmosdb mongodb database createcommand. In this example, the database is named contoso-ship-manager.az cosmosdb mongodb database create \ --account-name $COSMOSDB_ACCOUNT_NAME \ --resource-group $RESOURCE_GROUP \ --name contoso-ship-managerVerify the database was successfully created using the
az cosmosdb mongodb database listcommand.az cosmosdb mongodb database list \ --account-name $COSMOSDB_ACCOUNT_NAME \ --resource-group $RESOURCE_GROUP \ --output tableYour output should look similar to the following example output:
Name ResourceGroup -------------------- --------------- contoso-ship-manager rg-ship-manager
Now that you created an external state to store all the data from the ship manager application, let's create the AKS resource to store the application itself.
Create the AKS cluster
For AKS, verify that the resource provider
Microsoft.ContainerServiceis registered in your subscription.az provider show --namespace Microsoft.ContainerService --query "registrationState"If the output is
NotRegistered, register the resource provider.az provider register --namespace Microsoft.ContainerServiceCreate a Bash variable to store the cluster name for use throughout the rest of the module.
AKS_CLUSTER_NAME=ship-manager-clusterCreate an AKS cluster using the
az aks createcommand.az aks create --resource-group $RESOURCE_GROUP \ --name $AKS_CLUSTER_NAME \ --node-count 3 \ --generate-ssh-keys \ --node-vm-size Standard_B2s \ --enable-app-routingThe creation process can take a few moments to complete.
Note
All Azure services set default limits and quotas for resources and features, including usage restrictions for certain virtual machine (VM) SKUs. If you encounter an error suggesting your desired VM SKU is not available in the region you've selected, you most likely need to increase this quota through an Azure support request (for Issue type, select Quota).
Download the kubectl configuration using the
az aks get-credentialscommand.az aks get-credentials --name $AKS_CLUSTER_NAME --resource-group $RESOURCE_GROUPIf you receive any messages about existing clusters, for example:
A different object named ship-manager-cluster already exists in your kubeconfig file. Overwrite? (y/n):Enter
yto overwrite.Test the configuration using the
kubectl get nodescommand.kubectl get nodesYour output should look similar to the following example output:
NAME STATUS ROLES AGE VERSION aks-nodepool1-12345678-vmss000000 Ready <none> 107s v1.33.7 aks-nodepool1-12345678-vmss000001 Ready <none> 104s v1.33.7 aks-nodepool1-12345678-vmss000002 Ready <none> 107s v1.33.7
Deploy the application
To create the application, you need to create the YAML files to deploy to Kubernetes. You can create the files on your computer and then upload them to your Bash session in Cloud Shell. In the Cloud Shell session select Manage files > Upload.
Deploy the back-end API
Export your Azure Cosmos DB database connection string to a variable using the
az cosmosdb keys listcommand.export COSMOS_CONNECTION_STRING=$(az cosmosdb keys list \ --type connection-strings \ --resource-group $RESOURCE_GROUP \ --name $COSMOSDB_ACCOUNT_NAME \ --query "connectionStrings[0].connectionString" --output tsv)Important
Don't use connection strings for a production environment because the connection string contains sensitive information. For more information, see the security overview for Azure Cosmos DB.
Create a new file named backend-deploy.yml and paste in the following deployment specification:
apiVersion: apps/v1 kind: Deployment metadata: name: ship-manager-backend spec: replicas: 1 selector: matchLabels: app: ship-manager-backend template: metadata: labels: app: ship-manager-backend spec: containers: - image: mcr.microsoft.com/mslearn/samples/contoso-ship-manager:backend name: ship-manager-backend resources: requests: cpu: 100m memory: 128Mi limits: cpu: 250m memory: 256Mi ports: - containerPort: 3000 name: http env: - name: DATABASE_MONGODB_URI value: "${COSMOS_CONNECTION_STRING}" - name: DATABASE_MONGODB_DBNAME value: contoso-ship-managerSave the file and upload it to Cloud Shell session by selecting Manage files > Upload.
Apply the back-end API deployment using the
kubectl applycommand.envsubst '${COSMOS_CONNECTION_STRING}' < backend-deploy.yml | kubectl apply -f -The command uses
envsubstto replace the${COSMOS_CONNECTION_STRING}placeholder in the YAML file with the value of theCOSMOS_CONNECTION_STRINGenvironment variable. The dash (-) at the end of the command tellskubectlto read the input from the standard input stream. The value of theCOSMOS_CONNECTION_STRINGvariable isn't saved to the file you uploaded to Cloud Shell.You should see a message similar to the following example output:
deployment.apps/ship-manager-backend created
To make this application available to everyone, you need to create a service and an ingress to take care of the traffic.
Create a new file named backend-network.yml and paste in the following networking specification:
apiVersion: v1 kind: Service metadata: name: ship-manager-backend spec: type: ClusterIP ports: - port: 80 targetPort: 3000 selector: app: ship-manager-backend --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ship-manager-backend spec: ingressClassName: webapprouting.kubernetes.azure.com rules: - host: http: paths: - backend: service: name: ship-manager-backend port: number: 80 path: /api pathType: PrefixSave the file and upload it to Cloud Shell session by selecting Manage files > Upload.
Apply the back-end networking deployment using the
kubectl applycommand.kubectl apply -f backend-network.ymlYour output should look similar to the following example output:
service/ship-manager-backend created ingress.networking.k8s.io/ship-manager-backend createdThis ingress routes requests to the back-end API on the
/apipath.Check the ingress status by querying Kubernetes for the available ingresses using the
kubectl get ingresscommand.kubectl get ingressOnce the ADDRESS field in the output is filled, it means the ingress was deployed and it's ready to be accessed, as shown in the following example output:
NAME CLASS HOSTS ADDRESS PORTS AGE ship-manager-backend webapprouting.kubernetes.azure.com * xx.xx.xx.xx 80 2m40sThe
HOSTSvalue is*because this ingress doesn't specify ahost:rule. Ingress host names are optional. When you omithost:, the ingress matches requests for any host name, and you access the app using the ingress ADDRESS.Store the ingress address in a variable. You'll use it for the front-end configuration.
export HOST_NAME=$(kubectl get ingress ship-manager-backend -o jsonpath='{.status.loadBalancer.ingress[0].ip}{.status.loadBalancer.ingress[0].hostname}')
Deploy the front-end interface
Create a new file named frontend-deploy.yml and paste in the following deployment specification:
apiVersion: apps/v1 kind: Deployment metadata: name: ship-manager-frontend spec: replicas: 1 selector: matchLabels: app: ship-manager-frontend template: metadata: labels: app: ship-manager-frontend spec: containers: - image: mcr.microsoft.com/mslearn/samples/contoso-ship-manager:frontend name: ship-manager-frontend imagePullPolicy: Always resources: requests: cpu: 100m memory: 128Mi limits: cpu: 250m memory: 256Mi ports: - containerPort: 80 volumeMounts: - name: config mountPath: /usr/src/app/dist/config.js subPath: config.js volumes: - name: config configMap: name: frontend-config --- apiVersion: v1 kind: ConfigMap metadata: name: frontend-config data: config.js: | const config = (() => { return { 'VUE_APP_BACKEND_BASE_URL': 'http://${HOST_NAME}', } })()Save the file and upload it to Cloud Shell session by selecting Manage files > Upload.
Apply the front-end deployment using the
kubectl applycommand.envsubst '${HOST_NAME}' < frontend-deploy.yml | kubectl apply -f -Your output should look similar to the following example output:
deployment.apps/ship-manager-frontend created configmap/frontend-config created
Next, you can create the networking resources that this application needs to be open to the web.
Create a new file named frontend-network.yml and paste in the following networking specification:
apiVersion: v1 kind: Service metadata: name: ship-manager-frontend spec: type: ClusterIP ports: - port: 80 targetPort: 80 selector: app: ship-manager-frontend --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ship-manager-frontend spec: ingressClassName: webapprouting.kubernetes.azure.com rules: - host: http: paths: - backend: service: name: ship-manager-frontend port: number: 80 path: / pathType: PrefixSave the file and upload it to Cloud Shell session by selecting Manage files > Upload.
Apply the front-end networking deployment using the
kubectl applycommand.kubectl apply -f frontend-network.ymlYour output should look similar to the following example output:
service/ship-manager-frontend created ingress.networking.k8s.io/ship-manager-frontend createdYou can access the application in your browser using
http://$HOST_NAME. Runecho $HOST_NAMEto display the value of theHOST_NAMEvariable.Check the ingress status by querying Kubernetes for the available ingresses using the
kubectl get ingresscommand.kubectl get ingressWhen the ADDRESS field in the output is filled, it means the ingress was deployed and is ready to be accessed, as shown in the following example output:
NAME CLASS HOSTS ADDRESS PORTS AGE ship-manager-backend webapprouting.kubernetes.azure.com * xx.xx.xx.xx 80 2m40s ship-manager-frontend webapprouting.kubernetes.azure.com * xx.xx.xx.xx 80 100sYou can now access the application at
http://$HOST_NAME.
Clear your variables
It's a good practice to clear variable values, especially when they contain sensitive data like the database connection string.
unset COSMOS_CONNECTION_STRING