Deep Dive into Working with Draft
Steven Murawski
Posted on September 1, 2023
What is Draft and what does it do?
Draft exists to help folks get up and running on Kubernetes more easily.
This command line tool is run in your project's working directory and attempts to identify the type of project in the repository.
From there, Draft will ask you some questions and generate all the files you need to containerize the application and deploy into a Kubernetes cluster.
In addition to the Dockerfile, Draft can provide Helm
charts, kustomize
configuration files, or plain old Kubernetes manifests.
Once your project has those configuration files, Draft can help you set up GitHub actions to build, package, and deploy your application into a Kubernetes cluster.
How does Draft Work?
Draft has several subcommands that we'll look at in depth.
First is draft create
which will generate the Dockerfile and any supporting configuration files necessary.
Second is draft setup-gh
which configures GitHub OpenID Connect with Microsoft Entra ID (formerly Azure Active Directory).
The third subcommand is draft generate-workflow
which creates GitHub workflow files to build, package, and deploy your application into Kubernetes.
The fourth main subcommand is draft update
which can update your configuration files to allow your application to be accessible from outside the cluster.
There are a few other minor features that we'll review as well.
Setting up an Azure Environment
To create a basic environment to test with Draft, we can use the Bicep template that I've included in my sample repo. It will create a resource group called drafttesting
with an AKS cluster and Azure Container Registry in the region you specify.
git clone https://github.com/smurawski/drafttesting
cd drafttesting
# I deployed to eastus, but pick a region that fits for you.
az deployment sub create --template-file ./deploy/main.bicep --location eastus
Setting up a test project
To start, I'm going to create a simple Go project. (or feel free to copy it from my sample repo
mkdir DraftTesting
cd DraftTesting
go mod init DraftTesting
And add a file main.go
(which I borrowed from learn.microsoft.com)
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", HelloServer)
http.ListenAndServe(":8080", nil)
}
func HelloServer(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}
Getting Started with draft create
After installing Draft, while still in the DraftTesting
directory, I'll run draft create
.
Dockerfile generation
Draft detects that I've got a Go project and asks if I want to use Go Modules. Other languages will have appropriate questions asked for their specific needs.
~/source/DraftTesting ❯ draft create
[Draft] --- Detecting Language ---
v yes
[Draft] --> Draft detected Go (88.475836%)
Once the language is identified, Draft moves on to create a Dockerfile. You'll be asked for the port to expose in the Dockerfile. My app is listening on 8080, so I'll use that. Then it asks for the version of Go my module requires, which for me was 1.21.
[Draft] --- Dockerfile Creation ---
Please enter the port exposed in the application (default: 80): 8080
Please enter the version of go used by the application (default: 1.18): 1.21.1
[Draft] --> Creating Dockerfile...
Now, I have a Dockerfile.
FROM golang:1.21
ENV PORT 8080
EXPOSE 8080
WORKDIR /go/src/app
COPY . .
RUN go mod vendor
RUN go build -v -o app
RUN mv ./app /go/bin/
CMD ["app"]
Generating Kubernetes Manifests
Draft continues to ask me which type of deployment I want to use. I'll keep it simple and pick manifests
.
[Draft] --- Deployment File Creation ---
v manifests
In one of the more annoying bits of the workflow, I get asked for the exposed port again.
Please enter the port exposed in the application: 8080
Draft prompts me to enter a name for the application.
Please enter the name of the application: drafttesting
Continuing on with Draft's fascination with ports, it asks which port should be used to expose my application outside the cluster, though it makes the default my previously selected port.
Please enter the port the service uses to make the application accessible from outside the cluster (default: 8080):
Next, I'm asked for the namespace to deploy the application into. I took the default.
Please enter the namespace to place new resources in (default: default):
This leads to the first question where things could start to go REALLY wrong if we aren't paying attention. Draft wants to know the image name for the deployment. Here's where we'll need to know what container registry our application is going to be deployed to.
Thanks to the Bicep template we ran earlier, I do have an Azure Container Registry to which I can deploy my container. We need to prefix our container image name with URL of the container registry.
Please enter the name of the image to use in the deployment (default: drafttesting): acrxms72d.azurecr.io/drafttesting
Finally, we provide the tag to deploy.
Please enter the tag of the image to use in the deployment (default: latest): v1
Now, we have our manifests to deploy our application into the Kubernetes cluster.
[Draft] --> Creating manifests Kubernetes resources...
[Draft] Draft has successfully created deployment resources for your project
[Draft] Use 'draft setup-gh' to set up Github OIDC.
Draft created a manifests
folder in my project directory with a deployment.yaml
and a service.yaml
. I'm not going to reproduce them here, but if you want a bit more on these files, check out Kubernetes Fundamentals - Pods and Deployments and Kubernetes Fundamentals - Services and Ingress.
So, what templates are used by draft create
?
The templates are embedded in the tool and can be found in the source on GitHub. The Dockerfile templates are here and the deployment templates are here.
Securing Access to Azure for my GitHub Workflow with draft setup-gh
Draft suggested we move onto setting up GitHub OpenID Connect (OIDC), so let's get that done.
OpenID Connect with Microsoft Entra ID/Azure Active Directory
First, let's define what's going to happen. Draft is first going to create an Microsoft Entra App Registration.
Then, it will configure a federated credential for that app registration. The federated credential will define what GitHub resources can request a token to access my subscription via the application.
Finally, it creates a service principal for that application and assigns it permissions to the resource group specified. The application will use that service principal to access the resources for GitHub Actions.
Running draft setup-gh
First, Draft prompts me to enter an app registration name.
Enter app registration name: drafttesting
Then, I can pick the subscriptions I have access to based on my current az
CLI login session.
Enter app registration name: drafttesting
Use the arrow keys to navigate: ↓ ↑ → ←
v my-azure-subscription-guid
Then, I need to provide a resource group to provide access to. My AKS cluster and ACR instance are in the drafttesting
resource group I created earlier.
Enter resource group name: drafttesting
Next, we need to enter our GitHub organization (or user) and repository. As you can see, my naming skills are on point and very clever.
Enter github organization and repo (organization/repoName): smurawski/drafttesting
After that, Draft setups up OIDC between Microsoft Entra ID and GitHub. This can take a minute or two.
[Draft] Draft has successfully set up Github OIDC for your project 😃
[Draft] Use 'draft generate-workflow' to generate a Github workflow to build and deploy an application on AKS.
Troubleshooting the Federated Credential
To see what federated credential was created and the values supplied by Draft, you can use the Azure CLI.
applicationName=drafttesting
objectId=$(az ad app list --display-name $applicationName --query '[].id' -o tsv)
az ad app federated-credential list --id $objectId
This will output a configuration for your federated credential. Take note of the subject property. That's where the configuration for what can access the credential lives. You'll see values like repo:smurawski/drafttesting:ref:refs/heads/main
which means that for requests originating from changes to my main
branch, Microsoft Entra ID can provide access to my subscription.
The most likely mismatch will be attempting to deploy from a branch that was not specified when using draft setup-gh
.
Generating Build Automation with draft generate-workflow
With our GitHub OIDC set up and configured to allow access to my Azure subscription, we can create our workflow.
Draft first asks what type of deployment manifests we are using. We selected manifests earlier, so we'll keep that choice now.
DraftTesting on main ❯ draft generate-workflow
[Draft] --> Generating Github workflow
v manifests
Next, we are prompted for our Azure Container Registry name, our container image name, the resource group for our AKS cluster, and the cluster name.
Please enter the Azure container registry name: acrxms72d
Please enter the container image name: drafttesting
Please enter the Azure resource group of your AKS cluster: drafttesting
Please enter the AKS cluster name: aks1
Then we are prompted to enter which branch to deploy from and where in the project repository should be our Docker build context (I specified the default which is the project folder root).
Please enter the Github branch to automatically deploy from: main
Please enter the path to the Docker build context (default: .):
Draft then generated our workflow file and ensured that our deployment manifest had the correct repository and deployment, but it removed the tag. This is not a problem as the build created will replace the image at deployment time with one tagged and built in that GitHub Actions run.
[Draft] Draft has successfully generated a Github workflow for your project 😃
So, what templates are used by draft generate-workflow
?
The templates used for the workflows can be found here
Allowing Traffic to my Application with draft update
The draft update
subcommand supports enabling extensions to AKS. Currently, only webapp_routing
is supported.
You'll need a TLS certificate stored in Azure Key Vault to use this feature.
You can find the templates for this feature here.
Other draft
subcommands
There are a few other subcommands available.
draft completion
will provide autocompletion helpers for your particular shell.
DraftTesting on main ❯ draft completion --help
Generate the autocompletion script for draft for the specified shell.
See each sub-command's help for details on how to use the generated script.
Usage:
draft completion [command]
Available Commands:
bash Generate the autocompletion script for bash
fish Generate the autocompletion script for fish
powershell Generate the autocompletion script for powershell
zsh Generate the autocompletion script for zsh
draft info
will dump out all the support answers for the languages/runtimes and deployment options.
DraftTesting on main ❯ draft info
{
"supportedLanguages": [
{
"name": "javascript",
"displayName": "JavaScript",
"variableExampleValues": {
"VERSION": [
"10.16.3",
"12.16.3",
"14.15.4"
]
}
},
{
"name": "erlang",
"displayName": "Erlang",
"variableExampleValues": {
"BUILDERVERSION": [
"24.2-alpine"
],
"VERSION": [
"3.15"
]
}
},
{
"name": "go",
"displayName": "Go",
"variableExampleValues": {
"VERSION": [
"1.16",
"1.17",
"1.18",
"1.19"
]
}
},
{
"name": "php",
"displayName": "PHP",
"variableExampleValues": {
"BUILDERVERSION": [
"1"
],
"VERSION": [
"7.1-apache"
]
}
},
{
"name": "gradlew",
"displayName": "Gradle",
"variableExampleValues": {
"BUILDERVERSION": [
"jdk8",
"jdk11",
"jdk17",
"jdk19"
],
"VERSION": [
"8-jre",
"11-jre",
"17-jre",
"19-jre"
]
}
},
{
"name": "java",
"displayName": "Java",
"variableExampleValues": {
"BUILDERVERSION": [
"3-jdk-11"
],
"VERSION": [
"8-jre",
"11-jre",
"17-jre",
"19-jre"
]
}
},
{
"name": "python",
"displayName": "Python",
"variableExampleValues": {
"ENTRYPOINT": [
"app.py",
"main.py"
],
"VERSION": [
"3.9",
"3.8",
"3.7",
"3.6"
]
}
},
{
"name": "ruby",
"displayName": "Ruby",
"variableExampleValues": {
"VERSION": [
"3.1.2",
"2.6",
"2.5",
"2.4"
]
}
},
{
"name": "rust",
"displayName": "Rust",
"variableExampleValues": {
"VERSION": [
"1.70.0",
"1.65.0",
"1.60",
"1.54",
"1.53"
]
}
},
{
"name": "clojure",
"displayName": "Clojure",
"variableExampleValues": {
"VERSION": [
"8-jdk-alpine",
"11-jdk-alpine",
"17-jdk-alpine",
"19-jdk-alpine"
]
}
},
{
"name": "gradle",
"displayName": "Gradle",
"variableExampleValues": {
"BUILDERVERSION": [
"jdk8",
"jdk11",
"jdk17",
"jdk19"
],
"VERSION": [
"8-jre",
"11-jre",
"17-jre",
"19-jre"
]
}
},
{
"name": "swift",
"displayName": "Swift",
"variableExampleValues": {
"VERSION": [
"5.2",
"5.5"
]
}
},
{
"name": "csharp",
"displayName": "C#",
"variableExampleValues": {
"VERSION": [
"3.1",
"4.0",
"5.0",
"6.0"
]
}
},
{
"name": "gomodule",
"displayName": "Go Module",
"variableExampleValues": {
"VERSION": [
"1.16",
"1.17",
"1.18",
"1.19"
]
}
}
],
"supportedDeploymentTypes": [
"manifests",
"helm",
"kustomize"
]
}
And, of course there's draft version
which will dump out the version information for the build you are using.
DraftTesting on main ❯ draft version
version: 0.0.33
runtime SHA: 2142dbc745ac8284999f3384358680c7bc6f8570
Try it out!
Head over to the project releases and grab the version for your system. Try it out and give us some feedback via Issues or drop me a comment here!
Posted on September 1, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
April 28, 2021