Skip to content

ci: Unbounded Services in Kubernetes #3168

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 48 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
73a0de2
WIP - create kubernetes cluster for unbounded services and deploy to AKS
tippmar-nr May 21, 2025
780cdfb
Use loadbalancer and public IP everywhere
tippmar-nr May 22, 2025
9c97f50
Completely working at this point. Yay!
tippmar-nr May 27, 2025
162abbd
Take out push trigger for github workflow
tippmar-nr May 27, 2025
fbcfc63
Added a script to create the cluster, updated k8s manifests to take p…
tippmar-nr Jun 5, 2025
89d2cde
Various updates, new scripts
tippmar-nr Jun 6, 2025
1fb3e3a
Reverting secrets changes
tippmar-nr Jun 6, 2025
78b5a0e
Merge branch 'main' into ci/unbounded-k8s-cluster
tippmar-nr Jun 25, 2025
1959379
Update example-secrets.json to match current secrets config
tippmar-nr Jun 25, 2025
6b05b3a
Update k8s deployment workflow and create shell scripts for some steps
tippmar-nr Jun 25, 2025
2a65a17
Update azure login step
tippmar-nr Jun 25, 2025
863cc9c
update permissions
tippmar-nr Jun 25, 2025
06cb1ba
Add federated credential creation
tippmar-nr Jun 25, 2025
596fb03
More federated identity stuff
tippmar-nr Jun 25, 2025
eb20fd7
dummy edit to trigger workflow
tippmar-nr Jun 25, 2025
f164dcf
script permissions
tippmar-nr Jun 25, 2025
66ec7f3
role assignment fixes
tippmar-nr Jun 25, 2025
354fc0e
More role assignments
tippmar-nr Jun 25, 2025
18b79f9
Update create cluster script, add environment tag to deploy workflow,…
tippmar-nr Jun 26, 2025
0e47f54
add input to manual trigger
tippmar-nr Jun 26, 2025
2c74e29
fix redis manifest, comment out docker build/push temporarily
tippmar-nr Jun 26, 2025
457b865
update k8s manifests
tippmar-nr Jun 26, 2025
82dac09
Fix elastic manifests to use environment variables from secrets
tippmar-nr Jun 26, 2025
04af795
Fix mssql password issues
tippmar-nr Jun 26, 2025
d72c2db
More mssql image fixes
tippmar-nr Jun 26, 2025
5882348
mssql fixes, change manifests to only import necessary secrets from t…
tippmar-nr Jun 26, 2025
e63fd04
add configuration for deploying only selected images to ACR
tippmar-nr Jun 26, 2025
dae9088
remove push trigger
tippmar-nr Jun 26, 2025
d58a1b9
sort image names alphabetically
tippmar-nr Jun 26, 2025
7d343c9
use string input instead of checkboxes
tippmar-nr Jun 26, 2025
2dcef79
More input tweaking
tippmar-nr Jun 26, 2025
5ad6b1d
update restart services step
tippmar-nr Jun 26, 2025
a729b78
Fix syntax errors, modify couchbase memory requirements
tippmar-nr Jun 26, 2025
a50ea11
modify oracle manifest memory requirements
tippmar-nr Jun 26, 2025
498d9b5
update couchbase memory
tippmar-nr Jun 26, 2025
ec2e20c
Merge branch 'main' into ci/unbounded-k8s-cluster
tippmar-nr Jun 26, 2025
85a23de
Update mysql to use UTF8 character set
tippmar-nr Jun 26, 2025
ffecb63
reverting mysql charset change
tippmar-nr Jun 26, 2025
f66a8a0
Updates MySql.Data versions under test to 8.0.28 or newer for compati…
tippmar-nr Jun 26, 2025
ab95754
Removing memory requests from couchbase and oracle as an experiment
tippmar-nr Jun 27, 2025
dfea5d6
Add liveness probe for couchbase
tippmar-nr Jun 27, 2025
b67db97
Update couchbase tests to work with newer library versions
tippmar-nr Jun 27, 2025
7d80a1c
Update image pull policy for all services
tippmar-nr Jun 27, 2025
ef31290
remove couchbase liveness probe
tippmar-nr Jun 27, 2025
38b64a4
Update couchbase dockerfile to install jq
tippmar-nr Jun 27, 2025
afeb7e0
Add UnboundedServices folder to ignore list for check-modified-files …
tippmar-nr Jun 27, 2025
036a80a
don't echo password when starting couchbase
tippmar-nr Jun 27, 2025
de4441a
ignore unbounded services changes in all_solutions workflow also
tippmar-nr Jun 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/all_solutions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
branches:
- main
- "feature/**"
paths-ignore:
- "tests/Agent/IntegrationTests/UnboundedServices/**"

release:
types: [published]
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/check_modified_files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ jobs:
filters: |
non-workflow-files-changed:
- '!.github/**'
- '!tests/Agent/IntegrationTests/UnboundedServices/**'
list-files: 'csv'

152 changes: 148 additions & 4 deletions .github/workflows/deploy-k8s-unboundedservices.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,156 @@ name: Deploy UnboundedServices to AKS

on:
workflow_dispatch:
inputs:
images_to_build:
description: "Images to build and push (optional; valid values: couchbase elastic elastic7 mongodb32 mongodb60 mssql mysql oracle postgres rabbitmq redis)"
required: false
type: string
default: ""
deploy_to_aks:
description: "Deploy to AKS"
required: false
type: boolean
default: false
restart_services:
description: "Restart services"
required: false
type: boolean
default: false

permissions:
id-token: write # Required for Azure login
contents: read

jobs:
placeholder:
name: Placeholder Job
build-and-deploy:
runs-on: ubuntu-latest
environment: integration-test # required for azure login; federated credentials reference this environment
env:
ACR_NAME: ${{ secrets.AZURE_UNBOUNDED_SERVICES_REGISTRY_NAME }}
RESOURCE_GROUP: ${{ secrets.AZURE_INTEGRATION_TEST_SERVICES_RESOURCE_GROUP }}
PUBLIC_IP_NAME: ${{ secrets.AZURE_K8S_PUBLIC_IP_NAME }}
CLUSTER_NAME: ${{ secrets.AZURE_K8S_CLUSTER_NAME }}
steps:
- name: Do nothing
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
disable-sudo: true
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Azure Login
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
with:
client-id: ${{ secrets.AZURE_UNBOUNDED_SERVICES_MANAGED_IDENTITY_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_UNBOUNDED_SERVICES_MANAGED_IDENTITY_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Azure Container Registry Login
uses: azure/docker-login@15c4aadf093404726ab2ff205b2cdd33fa6d054c # v2.0.0
with:
login-server: ${{ secrets.AZURE_UNBOUNDED_SERVICES_REGISTRY_LOGIN_SERVER }}
username: ${{ secrets.AZURE_UNBOUNDED_SERVICES_REGISTRY_USERNAME }}
password: ${{ secrets.AZURE_UNBOUNDED_SERVICES_REGISTRY_PASSWORD }}
- name: Build and Push Docker Images to ACR
if: ${{ inputs.images_to_build != '' }}
shell: bash
run: |
chmod +x ./tests/Agent/IntegrationTests/UnboundedServices/build_and_push_acr.sh
cd ./tests/Agent/IntegrationTests/UnboundedServices

# Validate input
SERVICES="${{ inputs.images_to_build }}"
# Trim leading/trailing whitespace
SERVICES=$(echo "$SERVICES" | xargs)

# Validate each service name
VALID_SERVICES=("couchbase" "elastic" "elastic7" "mongodb32" "mongodb60" "mssql" "mysql" "oracle" "postgres" "rabbitmq" "redis")
INVALID_SERVICES=()

for service in $SERVICES; do
VALID=false
for valid_service in "${VALID_SERVICES[@]}"; do
if [ "$service" = "$valid_service" ]; then
VALID=true
break
fi
done

if [ "$VALID" = "false" ]; then
INVALID_SERVICES+=("$service")
fi
done

# If any invalid services, show error and exit
if [ ${#INVALID_SERVICES[@]} -gt 0 ]; then
echo "Error: Invalid service names found: ${INVALID_SERVICES[*]}"
echo "Valid service names are: ${VALID_SERVICES[*]}"
exit 1
fi

echo "Building selected services: $SERVICES"
./build_and_push_acr.sh $ACR_NAME $RESOURCE_GROUP $SERVICES
- name: Create Kubernetes Secrets Manifest
if: ${{ inputs.deploy_to_aks == true }}
env:
TEST_SECRETS: ${{ secrets.TEST_SECRETS }}
run: |
chmod +x ./tests/Agent/IntegrationTests/UnboundedServices/create_k8s_secrets.sh
cd ./tests/Agent/IntegrationTests/UnboundedServices
./create_k8s_secrets.sh
- name: Set AKS Context
if: ${{ inputs.deploy_to_aks == true || inputs.restart_services == true }}
uses: azure/aks-set-context@v3
with:
resource-group: ${{ env.RESOURCE_GROUP }}
cluster-name: ${{ env.CLUSTER_NAME }}
- name: Get the public IP address from Azure and set it as an environment variable
if: ${{ inputs.deploy_to_aks == true }}
run: |
PUBLIC_IP=$(az network public-ip show --resource-group $RESOURCE_GROUP --name $PUBLIC_IP_NAME --query ipAddress -o tsv)
echo "PUBLIC_IP=$PUBLIC_IP" >> $GITHUB_ENV
- name: Deploy to AKS
if: ${{ inputs.deploy_to_aks == true }}
run: |
chmod +x ./tests/Agent/IntegrationTests/UnboundedServices/deploy_to_aks.sh
cd ./tests/Agent/IntegrationTests/UnboundedServices
./deploy_to_aks.sh

- name: Restart all services
if: ${{ inputs.restart_services == true }}
run: |
echo "This is a placeholder workflow that doesn't actually do anything."
echo "Restarting all deployments to pull latest images..."

# Get list of deployments in the unbounded-services namespace
DEPLOYMENTS=$(kubectl get deployments -n unbounded-services -o name)

for deployment in $DEPLOYMENTS; do
# Extract deployment name without the "deployment.apps/" prefix
DEPLOY_NAME=$(echo $deployment | sed 's/deployment.apps\///')

echo "Force restarting $deployment"

# Get current pod(s) for this deployment
PODS=$(kubectl get pods -n unbounded-services -l app=$DEPLOY_NAME -o name)

# Force delete the pods
for pod in $PODS; do
echo "Force deleting $pod"
kubectl delete $pod -n unbounded-services --force --grace-period=0
done

# Restart the deployment
echo "Restarting $deployment"
kubectl rollout restart $deployment -n unbounded-services

# Wait briefly to allow new pods to start creating
sleep 5
done

# Wait for all rollouts to complete
for deployment in $DEPLOYMENTS; do
echo "Waiting for rollout of $deployment to complete..."
kubectl rollout status $deployment -n unbounded-services --timeout=300s || true
done

echo "All services have been restarted and are running with the latest images."
Binary file added kubectl
Binary file not shown.
63 changes: 38 additions & 25 deletions tests/Agent/IntegrationTests/Shared/OracleConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@

using System;
using System.Data.Common;
using System.Text.RegularExpressions;

namespace NewRelic.Agent.IntegrationTests.Shared
{
public class OracleConfiguration
public static class OracleConfiguration
{
private static string _oracleConnectionString;
private static string _oracleDataSource;
private static string _oracleServer;
private static string _oraclePort;
private static bool _parsedHostPort = false;

// example: "Data Source=1.2.3.4:4444/XE;User Id=SYSTEM;Password=oraclePassword;"
public static string OracleConnectionString
Expand Down Expand Up @@ -61,19 +63,7 @@ public static string OracleServer
{
get
{
if (_oracleServer == null)
{
try
{
var uri = new UriBuilder(OracleDataSource);
_oracleServer = uri.Host;
}
catch (Exception ex)
{
throw new Exception("OracleServer configuration is invalid.", ex);
}
}

EnsureHostPortParsed();
return _oracleServer;
}
}
Expand All @@ -82,20 +72,43 @@ public static string OraclePort
{
get
{
if (_oraclePort == null)
EnsureHostPortParsed();
return _oraclePort;
}
}

// Ensures host and port are parsed and cached only once
private static void EnsureHostPortParsed()
{
if (!_parsedHostPort)
{
try
{
try
{
var uri = new UriBuilder(OracleDataSource);
_oraclePort = uri.Port.ToString();
}
catch (Exception ex)
{
throw new Exception("OraclePort configuration is invalid.", ex);
}
ParseHostAndPort(OracleDataSource, out _oracleServer, out _oraclePort);
_parsedHostPort = true;
}
catch (Exception ex)
{
throw new Exception("OracleServer or OraclePort configuration is invalid.", ex);
}
}
}

return _oraclePort;
// Parses host and port from a data source string like host:port/service
private static void ParseHostAndPort(string dataSource, out string host, out string port)
{
// Regex matches host (hostname or IPv4), port, and ignores service name
var match = Regex.Match(dataSource, @"^(?<host>[^:/\[]+(?:\.[^:/\[]+)*)[:](?<port>\d+)");
if (match.Success)
{
host = match.Groups["host"].Value;
port = match.Groups["port"].Value;
}
else
{
host = string.Empty;
port = string.Empty;
throw new FormatException($"Could not parse host and port from data source: {dataSource}");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


using System;
using System.Text.RegularExpressions;

namespace NewRelic.Agent.IntegrationTests.Shared
{
Expand All @@ -12,6 +13,7 @@ public class StackExchangeRedisConfiguration
private static string _stackExchangeRedisServer;
private static string _stackExchangeRedisPort;
private static string _stackExchangeRedisPassword;
private static bool _parsedHostPort = false;

// example: "1.2.3.4:4444"
public static string StackExchangeRedisConnectionString
Expand Down Expand Up @@ -39,19 +41,7 @@ public static string StackExchangeRedisServer
{
get
{
if (_stackExchangeRedisServer == null)
{
try
{
var uri = new UriBuilder(StackExchangeRedisConnectionString);
_stackExchangeRedisServer = uri.Host;
}
catch (Exception ex)
{
throw new Exception("StackExchangeRedisServer configuration is invalid.", ex);
}
}

EnsureHostPortParsed();
return _stackExchangeRedisServer;
}
}
Expand All @@ -60,20 +50,42 @@ public static string StackExchangeRedisPort
{
get
{
if (_stackExchangeRedisPort == null)
EnsureHostPortParsed();
return _stackExchangeRedisPort;
}
}

// Ensures host and port are parsed and cached only once
private static void EnsureHostPortParsed()
{
if (!_parsedHostPort)
{
try
{
try
{
var uri = new UriBuilder(StackExchangeRedisConnectionString);
_stackExchangeRedisPort = uri.Port.ToString();
}
catch (Exception ex)
{
throw new Exception("StackExchangeRedisPort configuration is invalid.", ex);
}
ParseHostAndPort(StackExchangeRedisConnectionString, out _stackExchangeRedisServer, out _stackExchangeRedisPort);
_parsedHostPort = true;
}
catch (Exception ex)
{
throw new Exception("StackExchangeRedisServer or StackExchangeRedisPort configuration is invalid.", ex);
}
}
}

return _stackExchangeRedisPort;
// Parses host and port from a connection string like host:port
private static void ParseHostAndPort(string connectionString, out string host, out string port)
{
var match = Regex.Match(connectionString, @"^(?<host>[^:/\[]+(?:\.[^:/\[]+)*)[:](?<port>\d+)");
if (match.Success)
{
host = match.Groups["host"].Value;
port = match.Groups["port"].Value;
}
else
{
host = string.Empty;
port = string.Empty;
throw new FormatException($"Could not parse host and port from connection string: {connectionString}");
}
}
public static string StackExchangeRedisPassword
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,14 @@
<PackageReference Include="EnterpriseLibrary.Data" Version="6.0.1304" Condition="'$(TargetFramework)' == 'net462'" />

<!-- MySql.Data framework references -->
<PackageReference Include="MySql.Data" Version="6.10.7" Condition="'$(TargetFramework)' == 'net462'" />
<PackageReference Include="MySql.Data" Version="8.0.15" Condition="'$(TargetFramework)' == 'net471'" />
<PackageReference Include="MySql.Data" Version="8.0.30" Condition="'$(TargetFramework)' == 'net48'" />
<!-- Oldest we can test with the "modern" MySql database we're running is 8.0.28 due to a character encoding bug -->
<PackageReference Include="MySql.Data" Version="8.0.28" Condition="'$(TargetFramework)' == 'net462'" />
<PackageReference Include="MySql.Data" Version="8.0.33" Condition="'$(TargetFramework)' == 'net471'" />
<PackageReference Include="MySql.Data" Version="8.4.0" Condition="'$(TargetFramework)' == 'net48'" />
<!-- MySql.Data v8.0.33 is a breaking change for the agent and requires agent version 10.11.1 or greater -->

<!-- MySql.Data .NET/Core references -->
<PackageReference Include="MySql.Data" Version="6.10.7" Condition="'$(TargetFramework)' == 'net8.0'" />
<PackageReference Include="MySql.Data" Version="8.0.28" Condition="'$(TargetFramework)' == 'net8.0'" />

<!-- mongocsharpdriver Legacy MongoDB driver references -->
<PackageReference Include="mongocsharpdriver" Version="1.10.0" Condition="'$(TargetFramework)' == 'net462'" />
Expand Down
Loading
Loading