1. Descripción general
Microsoft .NET Core es una versión de código abierto y multiplataforma de .NET que se puede ejecutar de forma nativa en contenedores. .NET Core está disponible en GitHub y Microsoft y la comunidad de .NET lo mantienen. En este lab, se implementa una app de .NET Core en contenedores en Google Kubernetes Engine (GKE).
En este lab, se sigue un patrón de desarrollo típico en el que las aplicaciones se desarrollan en el entorno local de los desarrolladores y, luego, se implementan en producción. En la primera parte del lab, se valida una app de ejemplo de .NET Core con un contenedor que se ejecuta en Cloud Shell. Una vez validada, la app se implementa en Kubernetes con GKE. El lab incluye los pasos para crear un clúster de GKE.
En la segunda parte del lab, se realiza un cambio menor en la app que muestra el nombre de host del contenedor que ejecuta esa instancia de la app. Luego, se valida la aplicación actualizada en Cloud Shell y se actualiza la implementación para que use la versión nueva. En la siguiente ilustración, se muestra la secuencia de actividades de este lab:

Costos
Si ejecutas este lab exactamente como se indica, se aplicarán los costos normales de los siguientes servicios:
2. Configuración y requisitos
Requisitos previos
Para completar este lab, se requieren una cuenta y un proyecto de Google Cloud. Para obtener instrucciones más detalladas sobre cómo crear un proyecto nuevo, consulta este codelab.
En este lab, se usa Docker que se ejecuta en Cloud Shell, que está disponible a través de la consola de Google Cloud y viene preconfigurado con muchas herramientas útiles, como gcloud y Docker. A continuación, se muestra cómo acceder a Cloud Shell. Haz clic en el ícono de Cloud Shell en la esquina superior derecha para que aparezca en el panel inferior de la ventana de la consola.

Opciones de configuración alternativas para el clúster de GKE (opcional)
Este lab requiere un clúster de Kubernetes. En la siguiente sección, se creará un clúster de GKE con una configuración simple. En esta sección, se muestran algunos comandos de gcloud que proporcionan opciones de configuración alternativas para usar cuando se compila un clúster de Kubernetes con GKE. Por ejemplo, con los siguientes comandos, es posible identificar diferentes tipos de máquinas, zonas y hasta GPUs (aceleradores).
- Enumera los tipos de máquinas con este comando
gcloud compute machine-types list - Enumera las GPUs con este comando
gcloud compute accelerator-types list - Enumera las zonas de procesamiento con este comando:
gcloud compute zones list - Obtén ayuda sobre cualquier comando de gcloud
gcloud container clusters --help- Por ejemplo, esto proporciona detalles sobre la creación de un clúster de Kubernetes
gcloud container clusters create --help.
- Por ejemplo, esto proporciona detalles sobre la creación de un clúster de Kubernetes
Para obtener una lista completa de las opciones de configuración de GKE, consulta este documento.
Preparación para crear el clúster de Kubernetes
En Cloud Shell, es necesario configurar algunas variables de entorno y el cliente de gcloud. Esto se logra con los siguientes comandos.
export PROJECT_ID=YOUR_PROJECT_ID
export DEFAULT_ZONE=us-central1-c
gcloud config set project ${PROJECT_ID}
gcloud config set compute/zone ${DEFAULT_ZONE}
Crea un clúster de GKE
Dado que este lab implementa la app de .NET Core en Kubernetes, es necesario crear un clúster. Usa el siguiente comando para crear un nuevo clúster de Kubernetes en Google Cloud con GKE.
gcloud container clusters create dotnet-cluster \
--zone ${DEFAULT_ZONE} \
--num-nodes=1 \
--node-locations=${DEFAULT_ZONE},us-central1-b \
--enable-stackdriver-kubernetes \
--machine-type=n1-standard-1 \
--workload-pool=${PROJECT_ID}.svc.id.goog \
--enable-ip-alias
--num-nodeses la cantidad de nodos que se agregarán por zona y se puede ajustar más adelante.--node-locationses una lista de zonas separadas por comas. En este caso, se usan la zona que identificas en la variable de entorno anterior yus-central1-b.- NOTA: Esta lista no puede contener duplicados.
--workload-poolestablece la identidad de carga de trabajo para que las cargas de trabajo de GKE puedan acceder a los servicios de Google Cloud
Mientras se compila el clúster, se muestra lo siguiente:
Creating cluster dotnet-cluster in us-central1-b... Cluster is being deployed...⠼
Configura kubectl
La CLI de kubectl es la principal forma de interactuar con un clúster de Kubernetes. Para usarlo con el clúster nuevo que se acaba de crear, se debe configurar para que se autentique en el clúster. Esto se hace con el siguiente comando.
$ gcloud container clusters get-credentials dotnet-cluster --zone ${DEFAULT_ZONE}
Fetching cluster endpoint and auth data.
kubeconfig entry generated for dotnet-cluster.
Ahora deberías poder usar kubectl para interactuar con el clúster.
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-dotnet-cluster-default-pool-02c9dcb9-fgxj Ready <none> 2m15s v1.16.13-gke.401
gke-dotnet-cluster-default-pool-ed09d7b7-xdx9 Ready <none> 2m24s v1.16.13-gke.401
3. Prueba de forma local y confirma la funcionalidad deseada
En este lab, se usan las siguientes imágenes de contenedor del repositorio oficial de .NET en Docker Hub.
Ejecuta el contenedor de forma local para verificar la funcionalidad
En Cloud Shell, ejecuta el siguiente comando de Docker para verificar que Docker se esté ejecutando correctamente y que el contenedor de .NET funcione según lo esperado:
$ docker run --rm mcr.microsoft.com/dotnet/samples
Hello from .NET!
__________________
\
\
....
....'
....
..........
.............'..'..
................'..'.....
.......'..........'..'..'....
........'..........'..'..'.....
.'....'..'..........'..'.......'.
.'..................'... ......
. ......'......... .....
. ......
.. . .. ......
.... . .......
...... ....... ............
................ ......................
........................'................
......................'..'...... .......
.........................'..'..... .......
........ ..'.............'..'.... ..........
..'..'... ...............'....... ..........
...'...... ...... .......... ...... .......
........... ....... ........ ......
....... '...'.'. '.'.'.' ....
....... .....'.. ..'.....
.. .......... ..'........
............ ..............
............. '..............
...........'.. .'.'............
............... .'.'.............
.............'.. ..'..'...........
............... .'..............
......... ..............
.....
Environment:
.NET 5.0.1-servicing.20575.16
Linux 5.4.58-07649-ge120df5deade #1 SMP PREEMPT Wed Aug 26 04:56:33 PDT 2020
Confirma la funcionalidad de la app web
También se puede validar una aplicación web de muestra en Cloud Shell. El siguiente comando docker run crea un contenedor nuevo que expone el puerto 80 y lo asigna al puerto localhost 8080. Recuerda que localhost en este caso está en Cloud Shell.
$ docker run -it --rm -p 8080:80 --name aspnetcore_sample mcr.microsoft.com/dotnet/samples:aspnetapp
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
No XML encryptor configured. Key {64a3ed06-35f7-4d95-9554-8efd38f8b5d3} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: /app
Dado que se trata de una app web, debe visualizarse y validarse en un navegador web. En la siguiente sección, se muestra cómo hacerlo en Cloud Shell con la vista previa web.
4. Accede a los servicios desde Cloud Shell con "Vista previa en la Web"
Cloud Shell ofrece Vista previa en la Web, una función que permite usar un navegador para interactuar con los procesos que se ejecutan en la instancia de Cloud Shell.
Usa la “Vista previa en la Web” para ver apps en Cloud Shell
En Cloud Shell, haz clic en el botón de vista previa en la Web y elige “Vista previa en el puerto 8080” (o el puerto que esté configurado para usar la vista previa en la Web).

Se abrirá una ventana del navegador con una dirección como la siguiente:
https://8080-cs-754738286554-default.us-central1.cloudshell.dev/?authuser=0
Cómo ver la aplicación de ejemplo de .NET con la vista previa web
Ahora puedes ver la app de ejemplo que se inició en el último paso. Para ello, inicia la versión preliminar web y carga la URL proporcionada. Debería verse algo similar a esto:

5. Implementar en Kubernetes
Compila el archivo YAML y aplícalo
En el siguiente paso, se requiere un archivo YAML que describa dos recursos de Kubernetes: una implementación y un servicio. Crea un archivo llamado dotnet-app.yaml en Cloud Shell y agrégale el siguiente contenido.
apiVersion: apps/v1
kind: Deployment
metadata:
name: dotnet-deployment
labels:
app: dotnetapp
spec:
replicas: 3
selector:
matchLabels:
app: dotnetapp
template:
metadata:
labels:
app: dotnetapp
spec:
containers:
- name: dotnet
image: mcr.microsoft.com/dotnet/samples:aspnetapp
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: dotnet-service
spec:
selector:
app: dotnetapp
ports:
- protocol: TCP
port: 8080
targetPort: 80
Ahora usa kubectl para aplicar este archivo a Kubernetes.
$ kubectl apply -f dotnet-app.yaml
deployment.apps/dotnet-deployment created
service/dotnet-service created
Observa los mensajes que indican que se crearon los recursos deseados.
Explora los recursos resultantes
Podemos usar la CLI de kubectl para examinar los recursos que se crearon anteriormente. Primero, veamos los recursos de Deployment y confirmemos que la nueva implementación esté allí.
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
dotnet-deployment 3/3 3 3 80s
A continuación, observa los ReplicaSets. El Deployment anterior debería haber creado un ReplicaSet.
$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
dotnet-deployment-5c9d4cc4b9 3 3 3 111s
Por último, observa los Pods. El Deployment indicó que debería haber tres instancias. El siguiente comando debería mostrar que hay tres instancias. Se agregó la opción -o wide para que se muestren los nodos en los que se ejecutan esas instancias.
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
dotnet-deployment-5c9d4cc4b9-cspqd 1/1 Running 0 2m25s 10.16.0.8 gke-dotnet-cluster-default-pool-ed09d7b7-xdx9 <none> <none>
dotnet-deployment-5c9d4cc4b9-httw6 1/1 Running 0 2m25s 10.16.1.7 gke-dotnet-cluster-default-pool-02c9dcb9-fgxj <none> <none>
dotnet-deployment-5c9d4cc4b9-vvdln 1/1 Running 0 2m25s 10.16.0.7 gke-dotnet-cluster-default-pool-ed09d7b7-xdx9 <none> <none>
Revisa el recurso de Service
Un recurso Service en Kubernetes es un balanceador de cargas. Los extremos se determinan según las etiquetas de los Pods. De esta manera, en cuanto se agregan Pods nuevos a la implementación a través de la operación kubectl scale deployment anterior, los Pods resultantes están disponibles de inmediato para las solicitudes que controla ese Servicio.
El siguiente comando debería mostrar el recurso Service.
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
dotnet-service ClusterIP 10.20.9.124 <none> 8080/TCP 2m50s
...
Puedes ver más detalles sobre el servicio con el siguiente comando.
$ kubectl describe svc dotnet-service
Name: dotnet-service
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=dotnetapp
Type: ClusterIP
IP: 10.20.9.124
Port: <unset> 8080/TCP
TargetPort: 80/TCP
Endpoints: 10.16.0.7:80,10.16.0.8:80,10.16.1.7:80
Session Affinity: None
Events: <none>
Ten en cuenta que el Service es de tipo ClusterIP. Esto significa que cualquier Pod dentro del clúster puede resolver el nombre del Service, dotnet-service, en su dirección IP. Las solicitudes enviadas al servicio se balancearán en todas las instancias (Pods). El valor Endpoints anterior muestra las IPs de los Pods disponibles actualmente para este servicio. Compara estas direcciones IP con las de los Pods que se generaron anteriormente.
Verifica la app en ejecución
En este punto, la aplicación está activa y lista para las solicitudes de los usuarios. Para acceder a ella, usa un proxy. El siguiente comando crea un proxy local que acepta solicitudes en el puerto 8080 y las pasa al clúster de Kubernetes.
$ kubectl proxy --port 8080
Starting to serve on 127.0.0.1:8080
Ahora, usa la Vista previa en la Web de Cloud Shell para acceder a la aplicación web.
Agrega lo siguiente a la URL que genera la Vista previa en la Web: /api/v1/namespaces/default/services/dotnet-service:8080/proxy/. El resultado final se verá de la siguiente manera:
https://8080-cs-473655782854-default.us-central1.cloudshell.dev/api/v1/namespaces/default/services/dotnet-service:8080/proxy/
Felicitaciones por implementar una app de .NET Core en Google Kubernetes Engine. A continuación, haremos un cambio en la app y la volveremos a implementar.
6. Modifica la app
En esta sección, se modificará la aplicación para mostrar el host en el que se ejecuta la instancia. Esto permitirá confirmar que el balanceo de cargas funciona y que los Pods disponibles responden según lo previsto.
Obtén el código fuente
git clone https://github.com/dotnet/dotnet-docker.git
cd dotnet-docker/samples/aspnetapp/
Actualiza la app para incluir el nombre de host
vi aspnetapp/Pages/Index.cshtml
<tr>
<td>Host</td>
<td>@Environment.MachineName</td>
</tr>
Compila una nueva imagen de contenedor y pruébala de forma local
Compila la nueva imagen de contenedor con el código actualizado.
docker build --pull -t aspnetapp:alpine -f Dockerfile.alpine-x64 .
Como antes, prueba la nueva aplicación de forma local
$ docker run --rm -it -p 8080:80 aspnetapp:alpine
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
No XML encryptor configured. Key {f71feb13-8eae-4552-b4f2-654435fff7f8} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: /app
Al igual que antes, se puede acceder a la app con la Vista previa en la Web. Esta vez, el parámetro Host debería estar visible, como se muestra aquí:

Abre una nueva pestaña en Cloud Shell y ejecuta docker ps para ver que el ID del contenedor coincida con el valor del host que se muestra arriba.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ab85ce11aecd aspnetapp:alpine "./aspnetapp" 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp relaxed_northcutt
Etiqueta y envía la imagen para que esté disponible en Kubernetes
La imagen debe etiquetarse y enviarse para que Kubernetes pueda extraerla. Comienza por enumerar las imágenes de contenedor y, luego, identifica la imagen deseada.
$ docker image list
REPOSITORY TAG IMAGE ID CREATED SIZE
aspnetapp alpine 95b4267bb6d0 6 days ago 110MB
A continuación, etiqueta esa imagen y envíala a Google Container Registry. Con el ID de la imagen anterior, se verá de la siguiente manera:
docker tag 95b4267bb6d0 gcr.io/${PROJECT_ID}/aspnetapp:alpine
docker push gcr.io/${PROJECT_ID}/aspnetapp:alpine
7. Vuelve a implementar la aplicación actualizada
Edita el archivo YAML
Vuelve al directorio en el que se guardó el archivo dotnet-app.yaml. Busca la siguiente línea en el archivo YAML:
image: mcr.microsoft.com/dotnet/core/samples:aspnetapp
Esto debe cambiarse para hacer referencia a la imagen de contenedor que se creó y se envió a gcr.io anteriormente.
image: gcr.io/PROJECT_ID/aspnetapp:alpine
No olvides modificarlo para usar tu PROJECT_ID. Cuando termines, debería verse de la siguiente manera:
image: gcr.io/myproject/aspnetapp:alpine
Aplica el archivo YAML actualizado
$ kubectl apply -f dotnet-app.yaml
deployment.apps/dotnet-deployment configured
service/dotnet-service unchanged
Observa que el recurso Deployment se muestra actualizado y el recurso Service se muestra sin cambios. Los Pods actualizados se pueden ver como antes con el comando kubectl get pod, pero esta vez agregaremos el -w, que supervisará todos los cambios a medida que se produzcan.
$ kubectl get pod -w
NAME READY STATUS RESTARTS AGE
dotnet-deployment-5c9d4cc4b9-cspqd 1/1 Running 0 34m
dotnet-deployment-5c9d4cc4b9-httw6 1/1 Running 0 34m
dotnet-deployment-5c9d4cc4b9-vvdln 1/1 Running 0 34m
dotnet-deployment-85f6446977-tmbdq 0/1 ContainerCreating 0 4s
dotnet-deployment-85f6446977-tmbdq 1/1 Running 0 5s
dotnet-deployment-5c9d4cc4b9-vvdln 1/1 Terminating 0 34m
dotnet-deployment-85f6446977-lcc58 0/1 Pending 0 0s
dotnet-deployment-85f6446977-lcc58 0/1 Pending 0 0s
dotnet-deployment-85f6446977-lcc58 0/1 ContainerCreating 0 0s
dotnet-deployment-5c9d4cc4b9-vvdln 0/1 Terminating 0 34m
dotnet-deployment-85f6446977-lcc58 1/1 Running 0 6s
dotnet-deployment-5c9d4cc4b9-cspqd 1/1 Terminating 0 34m
dotnet-deployment-85f6446977-hw24v 0/1 Pending 0 0s
dotnet-deployment-85f6446977-hw24v 0/1 Pending 0 0s
dotnet-deployment-5c9d4cc4b9-cspqd 0/1 Terminating 0 34m
dotnet-deployment-5c9d4cc4b9-vvdln 0/1 Terminating 0 34m
dotnet-deployment-5c9d4cc4b9-vvdln 0/1 Terminating 0 34m
dotnet-deployment-85f6446977-hw24v 0/1 Pending 0 2s
dotnet-deployment-85f6446977-hw24v 0/1 ContainerCreating 0 2s
dotnet-deployment-5c9d4cc4b9-cspqd 0/1 Terminating 0 34m
dotnet-deployment-5c9d4cc4b9-cspqd 0/1 Terminating 0 34m
dotnet-deployment-85f6446977-hw24v 1/1 Running 0 3s
dotnet-deployment-5c9d4cc4b9-httw6 1/1 Terminating 0 34m
dotnet-deployment-5c9d4cc4b9-httw6 0/1 Terminating 0 34m
El resultado anterior muestra la actualización progresiva a medida que se realiza. Primero, se inician los contenedores nuevos y, cuando se están ejecutando, se finalizan los contenedores antiguos.
Verifica la app en ejecución
En este punto, la aplicación se actualiza y está lista para las solicitudes de los usuarios. Al igual que antes, se puede acceder a ella a través de un proxy.
$ kubectl proxy --port 8080
Starting to serve on 127.0.0.1:8080
Ahora, usa la Vista previa en la Web de Cloud Shell para acceder a la aplicación web.
Agrega lo siguiente a la URL que genera la Vista previa en la Web: /api/v1/namespaces/default/services/dotnet-service:8080/proxy/. El resultado final se verá de la siguiente manera:
https://8080-cs-473655782854-default.us-central1.cloudshell.dev/api/v1/namespaces/default/services/dotnet-service:8080/proxy/
Confirma que el servicio de Kubernetes esté distribuyendo la carga
Actualiza esta URL varias veces y observa que el host cambia a medida que el servicio balancea la carga de las solicitudes en diferentes Pods. Compara los valores de Host con la lista de Pods anterior para ver que todos los Pods reciben tráfico.
Cómo escalar verticalmente las instancias
Escalar apps en Kubernetes es fácil. El siguiente comando escalará la implementación hasta 6 instancias de la aplicación.
$ kubectl scale deployment dotnet-deployment --replicas 6
deployment.apps/dotnet-deployment scaled
Los nuevos Pods y su estado actual se pueden ver con este comando.
kubectl get pod -w
Observa que, si actualizas la misma ventana del navegador, se muestra que el tráfico ahora se balancea en todos los Pods nuevos.
8. ¡Felicitaciones!
En este lab, se validó una aplicación web de muestra de .NET Core en un entorno de desarrollador y, luego, se implementó en Kubernetes con GKE. Luego, se modificó la app para que mostrara el nombre de host del contenedor en el que se ejecutaba. Luego, se actualizó la implementación de Kubernetes a la versión nueva y se escaló la app para demostrar cómo se distribuye la carga en instancias adicionales.
Para obtener más información sobre .NET y Kubernetes, consulta estos instructivos. Estos se basan en lo que se aprendió en este lab, ya que introducen la malla de servicios de Istio para patrones de enrutamiento y resiliencia más sofisticados.
9. Limpia
Para evitar costos no deseados, usa los siguientes comandos para borrar el clúster y la imagen del contenedor que se crearon en este lab.
gcloud container clusters delete dotnet-cluster --zone ${DEFAULT_ZONE}
gcloud container images delete gcr.io/${PROJECT_ID}/aspnetapp:alpine