Applications that we deploy in AKS clusters require AAD application credentials or managed identity to access AAD-protected resources. Azure AD Workload Identity for Kubernetes integrates with the capabilities native to Kubernetes to federate with external identity providers.
Create Admin Group in Azure Active Directory for AKS
1
groupId=$(az ad group create --display-name AKSADMINS --mail-nickname AKSADMINS --queryid-o tsv)
Add user to AKSADMINS group
1
2
userId=$(az ad user show --id <add user id, for me, it is my email> --queryid-o tsv)
az ad group member add --group$groupId--member-id$userId
Create Azure Kubernetes Service cluster
1
2
3
4
5
6
7
8
9
10
11
12
az aks create \--resource-group$resourceGroup\--name$aksName\--location eastus \--assign-identity$appId\--enable-managed-identity\--enable-oidc-issuer\--enable-workload-identity\--enable-aad\--aad-admin-group-object-ids$groupId\--node-count 1 \--node-vm-size"Standard_B2s"
We can find a description of each flag in documentation.
Get AKS credentials
1
az aks get-credentials --resource-group$resourceGroup--name$aksName
Get the OIDC Issuer URL
1
oidcUrl="$(az aks show --name$aksName--resource-group$resourceGroup--query"oidcIssuerProfile.issuerUrl"-o tsv)"
Create Key Vault
1
2
az keyvault create --name$kvName\--resource-group$resourceGroup
Add OpenAI API key to Key Vault secrets
1
2
3
az keyvault secret set--vault-name$kvName\--name"CHATGPT-API-KEY"\--value <your api key value>
Create Storage Account
1
2
3
4
5
6
7
az storage account create --name$storageName\--resource-group$resourceGroup\--allow-blob-public-accessfalse\--kind StorageV2 \--public-network-access"Enabled"\--sku"Standard_LRS"\--encryption-services blob
Add Storage Container
1
2
3
az storage container create \--account-name$storageName\--name"chatgpt"
Create a user-assigned managed identity for workload identity
1
az identity create --name$workloadIdentity--resource-group$resourceGroup
Grant permission to access the secret in Key Vault
1
2
3
4
5
clientId=$(az identity show --name$workloadIdentity--resource-group$resourceGroup--query clientId -o tsv)
az keyvault set-policy --name$kvName\--secret-permissions get \--spn$clientId
Grant permission to access Storage Account
1
2
3
4
5
storageId=$(az storage account show --name$storageName--resource-group$resourceGroup--queryid-o tsv)
az role assignment create \--assignee$clientId\--role'Storage Blob Data Contributor'\--scope$storageId
Create Kubernetes service account
Create a sa.yaml file and insert a clientId of managed identity.
az identity federated-credential create \--name"aks-federated-credential"\--identity-name$workloadIdentity\--resource-group$resourceGroup\--issuer"${oidcUrl}"\--subject"system:serviceaccount:default:workload-sa"
# Imports
importjsonimportloggingimportosimportsecretsfromdataclassesimportdataclassfromaiohttpimportClientSessionfromazure.keyvault.secrets.aioimportSecretClientfromazure.keyvault.secrets._modelsimportKeyVaultSecretfromazure.identity.aioimportDefaultAzureCredentialfromazure.storage.blob.aioimportBlobServiceClientfromfastapiimportAPIRouterfromfastapiimportstatusfrompydanticimportBaseModel# FastApi router
router=APIRouter()# Pydantic model
classQuestion(BaseModel):prompt:str# Retrieve secret `CHATGPT-API-KEY` from Key Vault
@dataclassclassKvSecretHandler:kv_url:str=os.getenv("KV-URL","")secret_name:str="CHATGPT-API-KEY"asyncdefget_secret(self)->KeyVaultSecret:default_credential=DefaultAzureCredential()kv_client=SecretClient(vault_url=self.kv_url,credential=default_credential)asyncwithkv_client:asyncwithdefault_credential:returnawaitkv_client.get_secret(self.secret_name)# Save response from ChatGPT to txt file
# and upload it to `chatgpt` container in Storage Account.
@dataclassclassStorageBlobHandler:account_url=f"https://{os.getenv('ACCOUNT-NAME','')}.blob.core.windows.net"asyncdefupload_blob(self,blob_answer:bytes)->None:file_name=secrets.token_urlsafe(5)default_credential=DefaultAzureCredential()blob_service_client=BlobServiceClient(account_url=self.account_url,credential=default_credential)asyncwithblob_service_client:asyncwithdefault_credential:container_client=blob_service_client.get_container_client(container="chatgpt")blob_client=container_client.get_blob_client(blob=f"{file_name}.txt")awaitblob_client.upload_blob(data=blob_answer)# Call OpenAI API with question and return response
@dataclassclassChatGptApiCallHandler:asyncdefprocess_question(self,chatgpt_key:str,prompt:str)->bytes:headers={"Authorization":f"Bearer {chatgpt_key}","Content-Type":"application/json"}payload={"model":"text-davinci-003","prompt":prompt,"max_tokens":100,"temperature":0}asyncwithClientSession(headers=headers)assession:asyncwithsession.post(url="https://api.openai.com/v1/completions",data=json.dumps(payload))asresponse:returnawaitresponse.read()# Application endpoint to POST question, http://localhost:8000/api/chatgpt
@router.post("/chatgpt",status_code=status.HTTP_201_CREATED)asyncdefsend_question(question:Question)->None:chatgpt_key=awaitKvSecretHandler().get_secret()chatgpt_response=awaitChatGptApiCallHandler().process_question(chatgpt_key.value,question.prompt)awaitStorageBlobHandler().upload_blob(chatgpt_response)
Get Key Vault URL
1
kvUrl=$(az keyvault show --name$kvName--resource-group$resourceGroup--query properties.vaultUri -o tsv)