registry/deploy/pkg/k8s/registry.go
adam jones 01a6ce5fb2
infra: Update Ingress configuration to support prod registry domain at registry.modelcontextprotocol.io (#266)
i.e.

```diff
staging.registry.modelcontextprotocol.io -> staging
prod.registry.modelcontextprotocol.io -> prod
+ registry.modelcontextprotocol.io -> prod
```
2025-08-13 19:12:25 +01:00

217 lines
6.9 KiB
Go

package k8s
import (
v1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
networkingv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/networking/v1"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
"github.com/modelcontextprotocol/registry/deploy/infra/pkg/providers"
)
// DeployMCPRegistry deploys the MCP Registry to the Kubernetes cluster
func DeployMCPRegistry(ctx *pulumi.Context, cluster *providers.ProviderInfo, environment string) (*corev1.Service, error) {
conf := config.New(ctx, "mcp-registry")
githubClientId := conf.Require("githubClientId")
githubClientSecret := conf.RequireSecret("githubClientSecret")
// Create Secret with sensitive configuration
secret, err := corev1.NewSecret(ctx, "mcp-registry-secrets", &corev1.SecretArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String("mcp-registry-secrets"),
Namespace: pulumi.String("default"),
Labels: pulumi.StringMap{
"app": pulumi.String("mcp-registry"),
"environment": pulumi.String(environment),
},
},
StringData: pulumi.StringMap{
"GITHUB_CLIENT_SECRET": githubClientSecret,
},
Type: pulumi.String("Opaque"),
}, pulumi.Provider(cluster.Provider))
if err != nil {
return nil, err
}
// Create Deployment
_, err = v1.NewDeployment(ctx, "mcp-registry", &v1.DeploymentArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String("mcp-registry"),
Namespace: pulumi.String("default"),
Labels: pulumi.StringMap{
"app": pulumi.String("mcp-registry"),
"environment": pulumi.String(environment),
},
},
Spec: &v1.DeploymentSpecArgs{
Replicas: pulumi.Int(2),
Selector: &metav1.LabelSelectorArgs{
MatchLabels: pulumi.StringMap{
"app": pulumi.String("mcp-registry"),
},
},
Template: &corev1.PodTemplateSpecArgs{
Metadata: &metav1.ObjectMetaArgs{
Labels: pulumi.StringMap{
"app": pulumi.String("mcp-registry"),
},
},
Spec: &corev1.PodSpecArgs{
Containers: corev1.ContainerArray{
&corev1.ContainerArgs{
Name: pulumi.String("mcp-registry"),
Image: pulumi.String("ghcr.io/modelcontextprotocol/registry:latest"),
ImagePullPolicy: pulumi.String("Always"),
Ports: corev1.ContainerPortArray{
&corev1.ContainerPortArgs{
ContainerPort: pulumi.Int(8080),
Name: pulumi.String("http"),
},
},
Env: corev1.EnvVarArray{
&corev1.EnvVarArgs{
Name: pulumi.String("MCP_REGISTRY_DATABASE_URL"),
Value: pulumi.String("mongodb://mongodb.default.svc.cluster.local:27017"),
},
&corev1.EnvVarArgs{
Name: pulumi.String("MCP_REGISTRY_GITHUB_CLIENT_ID"),
Value: pulumi.String(githubClientId),
},
&corev1.EnvVarArgs{
Name: pulumi.String("MCP_REGISTRY_GITHUB_CLIENT_SECRET"),
ValueFrom: &corev1.EnvVarSourceArgs{
SecretKeyRef: &corev1.SecretKeySelectorArgs{
Name: secret.Metadata.Name(),
Key: pulumi.String("GITHUB_CLIENT_SECRET"),
},
},
},
},
LivenessProbe: &corev1.ProbeArgs{
HttpGet: &corev1.HTTPGetActionArgs{
Path: pulumi.String("/v0/health"),
Port: pulumi.Int(8080),
},
InitialDelaySeconds: pulumi.Int(30),
TimeoutSeconds: pulumi.Int(5),
},
ReadinessProbe: &corev1.ProbeArgs{
HttpGet: &corev1.HTTPGetActionArgs{
Path: pulumi.String("/v0/health"),
Port: pulumi.Int(8080),
},
InitialDelaySeconds: pulumi.Int(5),
TimeoutSeconds: pulumi.Int(3),
},
},
},
},
},
},
}, pulumi.Provider(cluster.Provider))
if err != nil {
return nil, err
}
// Create Service
service, err := corev1.NewService(ctx, "mcp-registry", &corev1.ServiceArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String("mcp-registry"),
Namespace: pulumi.String("default"),
Labels: pulumi.StringMap{
"app": pulumi.String("mcp-registry"),
"environment": pulumi.String(environment),
},
},
Spec: &corev1.ServiceSpecArgs{
Selector: pulumi.StringMap{
"app": pulumi.String("mcp-registry"),
},
Ports: corev1.ServicePortArray{
&corev1.ServicePortArgs{
Port: pulumi.Int(80),
TargetPort: pulumi.Int(8080),
Name: pulumi.String("http"),
},
},
Type: pulumi.String("ClusterIP"),
},
}, pulumi.Provider(cluster.Provider))
if err != nil {
return nil, err
}
// Create Ingress
hosts := []string{
environment + ".registry.modelcontextprotocol.io",
}
// Add root domain for prod environment
if environment == "prod" {
hosts = append(hosts, "registry.modelcontextprotocol.io")
}
ingress, err := networkingv1.NewIngress(ctx, "mcp-registry", &networkingv1.IngressArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String("mcp-registry"),
Namespace: pulumi.String("default"),
Labels: pulumi.StringMap{
"app": pulumi.String("mcp-registry"),
"environment": pulumi.String(environment),
},
Annotations: pulumi.StringMap{
"cert-manager.io/cluster-issuer": pulumi.String("letsencrypt-prod"),
"kubernetes.io/ingress.class": pulumi.String("nginx"),
},
},
Spec: &networkingv1.IngressSpecArgs{
Tls: networkingv1.IngressTLSArray{
&networkingv1.IngressTLSArgs{
Hosts: pulumi.ToStringArray(hosts),
SecretName: pulumi.Sprintf("mcp-registry-%s-tls", environment),
},
},
Rules: pulumi.ToStringArray(hosts).ToStringArrayOutput().ApplyT(func(hosts []string) networkingv1.IngressRuleArray {
rules := make(networkingv1.IngressRuleArray, 0, len(hosts))
for _, host := range hosts {
rules = append(rules, &networkingv1.IngressRuleArgs{
Host: pulumi.String(host),
Http: &networkingv1.HTTPIngressRuleValueArgs{
Paths: networkingv1.HTTPIngressPathArray{
&networkingv1.HTTPIngressPathArgs{
Path: pulumi.String("/"),
PathType: pulumi.String("Prefix"),
Backend: &networkingv1.IngressBackendArgs{
Service: &networkingv1.IngressServiceBackendArgs{
Name: service.Metadata.Name().Elem(),
Port: &networkingv1.ServiceBackendPortArgs{
Number: pulumi.Int(80),
},
},
},
},
},
},
})
}
return rules
}).(networkingv1.IngressRuleArrayOutput),
},
}, pulumi.Provider(cluster.Provider))
if err != nil {
return nil, err
}
ctx.Export("ingressHosts", ingress.Spec.Rules().ApplyT(func(rules []networkingv1.IngressRule) []string {
hosts := make([]string, 0, len(rules))
for _, rule := range rules {
hosts = append(hosts, *rule.Host)
}
return hosts
}))
return service, nil
}