1. Descripción general
Microsoft .NET Core es una versión de código abierto y multiplataforma de .NET que puede ejecutarse de forma nativa en contenedores. .NET Core está disponible en GitHub y se mantiene mediante Microsoft y la comunidad de .NET. En este lab, se implementará una aplicación de .NET Core alojada 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 un entorno local de desarrollador y, luego, se implementan en producción. En la primera parte del lab, se valida una aplicación principal de ejemplo de .NET mediante un contenedor que se ejecuta en Cloud Shell. Una vez validada, la app se implementa en Kubernetes mediante 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 aplicación que muestra el nombre de host del contenedor que ejecuta esa instancia de la aplicación. Luego, se valida la aplicación actualizada en Cloud Shell y se actualiza la implementación para usar 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 está escrito, se aplicarán los costos normales de los siguientes servicios
2. Configuración y requisitos
Requisitos previos
Para completar este lab, necesitas una cuenta y un proyecto de Google Cloud. Si quieres obtener instrucciones más detalladas para crear un proyecto nuevo, consulta este codelab.
En este lab, se utiliza la ejecución de Docker en Cloud Shell, que está disponible en 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 se muestre 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 próxima sección, se creará un clúster de GKE con una configuración sencilla. 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, si usas los siguientes comandos, es posible identificar diferentes tipos de máquinas, zonas y hasta GPU (aceleradores).
- Genera una lista de los tipos de máquina con este comando
gcloud compute machine-types list
- Genera una lista de las GPU con este comando
gcloud compute accelerator-types list
- Genera una lista de las zonas de procesamiento con este comando
gcloud compute zones list
- Obtén ayuda con cualquier comando de gcloud
gcloud container clusters --help
- Por ejemplo, aquí se proporcionan detalles para crear un clúster de Kubernetes
gcloud container clusters create --help
- Por ejemplo, aquí se proporcionan detalles para crear un clúster de Kubernetes
Para obtener una lista completa de las opciones de configuración de GKE, consulta este documento.
Prepárate para crear el clúster de Kubernetes
En Cloud Shell, es necesario establecer algunas variables de entorno y configurar 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
Debido a que este lab implementa la aplicación .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-nodes
es la cantidad de nodos que se agregarán por zona y se puede escalar más adelante.--node-locations
es una lista de zonas separadas por comas. En este caso, la zona que identificas en la variable de entorno anterior yus-central1-b
se usan- NOTA: Esta lista no puede contener duplicados
--workload-pool
establece la identidad de las cargas de trabajo de GKE para que 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 forma principal de interactuar con un clúster de Kubernetes. Para usarlo con el clúster nuevo que se acaba de crear, se debe configurar de modo 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ía ser posible 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. Realiza pruebas locales y confirma la funcionalidad deseada
En este lab, se utilizan las siguientes imágenes de contenedor del repositorio oficial de .NET en el concentrador de Docker.
Ejecuta el contenedor de forma local para verificar la funcionalidad
En Cloud Shell, ejecuta el siguiente comando de Docker para verificar que Docker esté funcionando correctamente y que el contenedor .NET funcione como se espera:
$ 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 las funciones de la app web
Una aplicación web de muestra también se puede validar en Cloud Shell. El comando Docker run que aparece a continuación crea un contenedor nuevo que expone el puerto 80
y lo asigna al puerto 8080
de localhost
. Recuerda que, en este caso, localhost
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
Como 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 en la 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.
Utiliza "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 en cualquier puerto que la Vista previa web esté configurada para usar).
Se abrirá una ventana del navegador con una dirección como la siguiente:
https://8080-cs-754738286554-default.us-central1.cloudshell.dev/?authuser=0
Visualiza la aplicación de muestra de .NET con la vista previa en la Web
Para ver la app de ejemplo que se inició en el último paso, ahora puedes iniciar la Vista previa en la Web y cargar la URL proporcionada. Debería verse algo similar a esto:
5. Implementar en Kubernetes
Compila el archivo YAML y aplica
El siguiente paso requiere un archivo YAML que describa dos recursos de Kubernetes: un Deployment y un Service. 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 antes. Primero, veamos los recursos del objeto Deployment y confirmemos que la nueva implementación exista.
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
dotnet-deployment 3/3 3 3 80s
A continuación, observa los ReplicaSets. Debería haber un ReplicaSet creado con la implementación anterior.
$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
dotnet-deployment-5c9d4cc4b9 3 3 3 111s
Por último, observa los Pods. El objeto Deployment indicó que debería haber tres instancias. El siguiente comando debería mostrar que hay tres instancias. Se agrega 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 Service
Un recurso de Servicio en Kubernetes es un balanceador de cargas. Los extremos están determinados por etiquetas en los Pods. De esta manera, apenas 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 maneja ese Service.
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 obtener más detalles sobre el Service 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 del 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 cargas de las solicitudes enviadas al servicio se balancearán en todas las instancias (Pods). El valor Endpoints
anterior muestra las IP de los Pods que están disponibles actualmente para este servicio. Compara estas direcciones con las IP de los Pods que se mostraron con anterioridad.
Verifica la app en ejecución
En este punto, la aplicación está activa y lista para recibir 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 en 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 será similar al siguiente:
https://8080-cs-473655782854-default.us-central1.cloudshell.dev/api/v1/namespaces/default/services/dotnet-service:8080/proxy/
Felicitaciones por implementar una app .NET Core en Google Kubernetes Engine. A continuación, realizaremos un cambio en la aplicación y volveremos a implementarla.
6. Modifica la app
En esta sección, la aplicación se modificará para mostrar el host en el que se ejecuta la instancia. Esto permitirá confirmar que el balanceo de cargas está funcionando y que los Pods disponibles responden como se espera.
Obtén el código fuente
git clone https://github.com/dotnet/dotnet-docker.git
cd dotnet-docker/samples/aspnetapp/
Actualiza la app para que incluya 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 aplicación nueva 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 mediante la Vista previa en la Web. Esta vez, el parámetro Host debería estar visible, como se muestra aquí:
Abre una pestaña nueva 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 a fin de que esté disponible para Kubernetes
La imagen se debe etiquetar y enviar 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
Luego, etiqueta esa imagen y envíala a Google Container Registry. Si usas el ID de 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 donde se guarda 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 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 que 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 -w
, que observará todos los cambios a medida que ocurren.
$ 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 ocurre. Primero, se inician los contenedores nuevos y, cuando se ejecutan, se finalizan los antiguos.
Verifica la app en ejecución
En este punto, la aplicación está actualizada y lista para recibir solicitudes de los usuarios. Como 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 en 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 será similar al siguiente:
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
Actualice esta URL varias veces y observe que el Host cambia a medida que el Service balancea las cargas de las solicitudes en diferentes Pods. Compara los valores del host con la lista de Pods anterior para ver que todos los Pods estén recibiendo tráfico.
Escala 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, verás que el tráfico se está balanceando 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 mediante GKE. Luego, la app se modificó para mostrar el nombre de host del contenedor en el que se ejecutaba. Luego, la implementación de Kubernetes se actualizó a la nueva versión y la aplicación se escaló verticalmente para demostrar cómo se distribuye la carga entre instancias adicionales.
Para obtener más información sobre .NET y Kubernetes, consulta estos instructivos. Estos conceptos se basan en lo aprendido en este lab, ya que presentan la malla de servicios de Istio para obtener patrones de enrutamiento y resiliencia más sofisticados.
9. Limpia
Para evitar costos no deseados, usa los siguientes comandos y borra el clúster y la imagen de 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