One place for hosting & domains

      Pasos recomendados para proteger un clúster de Kubernetes de DigitalOcean


      El autor seleccionó Open Sourcing Mental Illness para recibir una donación como parte del programa Write for DOnations.

      Introducción

      Kubernetes, la plataforma de orquestación de contenedores de código abierto, se está convirtiendo en la solución preferida para automatizar, escalar y administrar clústeres de alta disponibilidad. Como resultado de su creciente popularidad, la seguridad en ella se ha vuelto cada vez más relevante.

      Tomando en cuenta los elementos móviles que intervienen en Kubernetes y la variedad de escenarios de implementación, proteger Kubernetes a veces puede ser un proceso complejo. Debido a esto, el objetivo de este artículo es proporcionar una base de seguridad sólida para un clúster de Kubernetes de DigitalOcean (DOKS). Tenga en cuenta que este tutorial contempla las medidas básicas de seguridad para Kubernetes y está pensado para ser un punto de partida más que una guía exhaustiva. Para hallar pasos adicionales, consulte la documentación oficial de Kubernetes.

      A través de esta guía, completará pasos básicos para proteger su clúster de Kubernetes de DigitalOcean. Configurará la autenticación local segura con certificados TLS y SSL, concederá permisos a los usuarios locales con controles de acceso basados en roles (RBAC), concederá permisos a aplicaciones e implementaciones de Kubernetes con cuentas de servicio y establecerá límites de recursos con los controladores de admisión ResourceQuota y LimitRange.

      Requisitos previos

      Para completar este tutorial, necesitará lo siguiente:

      • Un clúster administrado por DigitalOcean de Kubernetes (DOKS) con 3 nodos estándares configurados cada uno con al menos 2 GB de RAM y 1 vCPU. Para hallar instrucciones detalladas sobre cómo crear un clúster DOKS, consulte nuestra guía de inicio rápido de Kubernetes. En este tutorial se utiliza DOKS 1.16.2-do.1.
      • Un cliente local configurado para administrar el clúster DOKS, con un archivo de configuración de clúster descargado del panel de control de DigitalOcean y guardado como ~/.kube/config. Para hallar instrucciones detalladas sobre cómo configurar la administración remota de DOKS, lea nuestra guía Cómo establecer conexión con un clúster de Kubernetes de DigitalOcean. En particular, necesitará lo siguiente:
        • La interfaz de línea de comandos kubectl instalada en su computadora local. Puede obtener más información sobre cómo instalar y configurar kubectl en su documentación oficial. En este tutorial, usaremos la versión 1.17.0-00 de kubectl.
        • La herramienta de línea de comandos oficial de DigitalOcean, doctl. Para hallar instrucciones sobre cómo instalar esto, consulte la página sobre doctl de GitHub. En este tutorial, se usará la versión 1.36.0 de doctl.

      Paso 1: Habilitar la autenticación remota de usuarios

      Una vez completados los requisitos previos, obtendrá un superusuario de Kubernetes que se autentica a través de un token de portador de DigitalOcean predefinido. Sin embargo, compartir esas credenciales no es una práctica de seguridad recomendada, ya que en esta cuenta se pueden ocasionar cambios a gran escala y posiblemente destructivos para su clúster. Para reducir esta posibilidad, puede configurar usuarios adicionales que se autentiquen desde sus respectivos clientes locales.

      En esta sección, autenticará nuevos usuarios en el clúster DOKS remoto de los clientes locales usando certificados SSL y TLS seguros. Será un proceso de tres pasos: primero, creará solicitudes de firma de certificados (CSR) para cada usuario. Luego, aprobará esos certificados directamente en el clúster a través de kubectl. Por último, creará para cada usuario un archivo kubeconfig con los certificados correspondientes. Para obtener más información sobre los métodos de autenticación adicionales compatibles con Kubernetes, consulte la documentación de autenticación de Kubernetes.

      Crear solicitudes de firma de certificados para nuevos usuarios

      Antes de comenzar, compruebe la conexión del clúster DOKS desde la máquina local configurada en los requisitos previos:

      Dependiendo de su configuración, el resultado será similar a este:

      Output

      Kubernetes master is running at https://a6616782-5b7f-4381-9c0f-91d6004217c7.k8s.ondigitalocean.com CoreDNS is running at https://a6616782-5b7f-4381-9c0f-91d6004217c7.k8s.ondigitalocean.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

      Esto significa que está conectado al clúster DOKS.

      A continuación, cree una carpeta local para los certificados de cliente. A los efectos de esta guía, se usará ~/certs para almacenar todos los certificados:

      En este tutorial, para acceder al clúster autorizaremos un nuevo usuario llamado sammy. Puede cambiarlo por un usuario que elija. Usando la biblioteca OpenSSL de SSL y TLS, genere una nueva clave privada para su usuario con el siguiente comando:

      • openssl genrsa -out ~/certs/sammy.key 4096

      Con el indicador -out se creará el archivo de salida ~/certs/sammy.key, y con 4096 se fija la clave en 4096 bits. Para obtener más información sobre OpenSSL, consulte nuestra guía Aspectos básicos de OpenSSL.

      A continuación, cree un archivo de configuración de la solicitud de firma de certificados. Abra el siguiente archivo con un editor de texto (para este tutorial, usaremos nano):

      • nano ~/certs/sammy.csr.cnf

      Añada el siguiente contenido al archivo sammy.csr.cnf para especificar en el tema el nombre de usuario deseado como nombre común (CN) y el grupo como organización (O):

      ~/certs/sammy.csr.cnf

      [ req ]
      default_bits = 2048
      prompt = no
      default_md = sha256
      distinguished_name = dn
      [ dn ]
      CN = sammy
      O = developers
      [ v3_ext ]
      authorityKeyIdentifier=keyid,issuer:always
      basicConstraints=CA:FALSE
      keyUsage=keyEncipherment,dataEncipherment
      extendedKeyUsage=serverAuth,clientAuth
      

      En el archivo de configuración de la solicitud de firma de certificados se incluyen toda la información necesaria, la identidad del usuario y los parámetros de uso adecuados para el usuario. El ltimo argumento extendedKeyUsage=serverAuth,clientAuth permitirá que los usuarios autentiquen sus clientes locales con el clúster DOKS usando el certificado una vez que se haya firmado.

      A continuación, cree la solicitud de firma de certificados sammy:

      • openssl req -config ~/certs/sammy.csr.cnf -new -key ~/certs/sammy.key -nodes -out ~/certs/sammy.csr

      Con -config se le permite especificar el archivo de configuración para la CSR, y -new indica que creará una nueva CSR para la clave especificada por -key.

      Puede verificar la solicitud de firma de certificados ejecutando el siguiente comando:

      • openssl req -in ~/certs/sammy.csr -noout -text

      Se pasa la CSR con -in y se utiliza -text para imprimir la solicitud del certificado en texto.

      En el resultado, se mostrará la solicitud del certificado, cuyo comienzo tendrá un aspecto similar a este:

      Output

      Certificate Request: Data: Version: 1 (0x0) Subject: CN = sammy, O = developers Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public-Key: (4096 bit) ...

      Repita el mismo procedimiento para crear CSR para cualquier usuario adicional. Una vez que tenga todas las solicitudes de firma de certificados guardadas en la carpeta del administrador ~/certs, proceda con el paso siguiente para aprobarlas.

      Administrar solicitudes de firma de certificados con la API de Kubernetes

      Puede aprobar o negar certificados TLS emitidos para la API de Kubernetes usando la herramienta de línea de comandos kubectl. Con esto, tendrá la capacidad de garantizar que el acceso solicitado sea apropiado para el usuario en cuestión. En esta sección, enviará la solicitud de certificado para sammy y la aprobará.

      Para enviar una CSR al clúster DOKS, utilice el siguiente comando:

      cat <<EOF | kubectl apply -f -
      apiVersion: certificates.k8s.io/v1beta1
      kind: CertificateSigningRequest
      metadata:
        name: sammy-authentication
      spec:
        groups:
        - system:authenticated
        request: $(cat ~/certs/sammy.csr | base64 | tr -d 'n')
        usages:
        - digital signature
        - key encipherment
        - server auth
        - client auth
      EOF
      

      Mediante un here document de Bash, este comando utiliza cat para pasar la solicitud de certificado a kubectl apply.

      Veamos con más detalle la solicitud de certificado:

      • Con name: sammy-authentication, se crea un identificador de metadatos llamado en este caso sammy-authentication.
      • Con request: $(cat ~/certs/sammy.csr | base64 | tr -d 'n') se envía la solicitud de firma de certificados sammy.csr al clúster, codificado como base64.
      • Con server auth y client auth se especifica el uso previsto del certificado. En este caso, el propósito es autenticar el usuario.

      El resultado tendrá un aspecto similar a este:

      Output

      certificatesigningrequest.certificates.k8s.io/sammy-authentication created

      Puede verificar el estado de la solicitud de firma de certificados usando el siguiente comando:

      Dependiendo de la configuración de su clúster, el resultado será similar a este:

      Output

      NAME AGE REQUESTOR CONDITION sammy-authentication 37s your_DO_email Pending

      A continuación, apruebe la CSR usando el siguiente comando:

      • kubectl certificate approve sammy-authentication

      Verá un mensaje que confirmará la operación:

      Output

      certificatesigningrequest.certificates.k8s.io/sammy-authentication approved

      Nota: Como administrador, también puede negar una CSR usando el comando kubectl certificate deny sammy-authentication. Para obtener más información sobre la administración de certificados TLS, consulte la documentación oficial de Kubernetes.

      Ahora que se aprobó la CSR, puede descargarla a la máquina local ejecutando lo siguiente:

      • kubectl get csr sammy-authentication -o jsonpath='{.status.certificate}' | base64 --decode > ~/certs/sammy.crt

      Con este comando, se decodifica el certificado de Base64 para que kubectl lo use correctamente; luego se guarda como ~/certs/sammy.crt.

      Una vez firmado el el certificado sammy, podrá crear el archivo kubeconfig del usuario.

      Crear Kubeconfig para usuarios remotos

      A continuación, creará un archivo kubeconfig específico para el usuario sammy. Esto le proporcionará más control sobre el acceso del usuario a su clúster.

      El primer paso para crear un nuevo kubeconfig es hacer una copia del archivo kubeconfig actual. A los efectos de esta guía, el nuevo archivo kubeconfig recibirá el nombre config-sammy:

      • cp ~/.kube/config ~/.kube/config-sammy

      A continuación, edite el nuevo archivo:

      • nano ~/.kube/config-sammy

      Conserve las primeras ocho líneas de este archivo, ya que contienen la información necesaria para la conexión SSL y TLS con el clúster. Luego, empezando por el parámetro de user, sustituya el texto por las siguientes líneas resaltadas de modo que el archivo tenga un aspecto similar al siguiente:

      config-sammy

      apiVersion: v1
      clusters:
      - cluster:
          certificate-authority-data: certificate_data
        name: do-nyc1-do-cluster
      contexts:
      - context:
          cluster: do-nyc1-do-cluster
          user: sammy
        name: do-nyc1-do-cluster
      current-context: do-nyc1-do-cluster
      kind: Config
      preferences: {}
      users:
      - name: sammy
        user:
          client-certificate: /home/your_local_user/certs/sammy.crt
          client-key: /home/your_local_user/certs/sammy.key
      

      Nota: Tanto para client-certificate como client-key, utilice la ruta absoluta hacia las ubicaciones de sus certificados correspondientes. De lo contrario, en kubectl se producirá un error.

      Guarde el archivo y ciérrelo.

      Puede probar la conexión del usuario nuevo usando kubectl cluster-info:

      • kubectl --kubeconfig=/home/your_local_user/.kube/config-sammy cluster-info

      Verá un error similar a este:

      Output

      To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. Error from server (Forbidden): services is forbidden: User "sammy" cannot list resource "services" in API group "" in the namespace "kube-system"

      Este error se espera, ya que el usuario sammy no tiene autorización para enumerar ningún recurso en el clúster. En el siguiente paso, se cubrirá la concesión de autorización a los usuarios. Por ahora, en el resultado se confirma que la conexión SSL y TLS fue exitosa y la API de Kubernetes aceptó las credenciales de autenticación de sammy.

      Paso 2: Autorizar usuarios a través del control de acceso basado en roles (RBAC)

      Una vez que un usuario se autentica, en la API se determinan sus permisos usando el modelo integrado de control de acceso basado en roles (RBAC) de Kubernetes. El RBAC es un método eficaz para restringir derechos de usuarios en función del rol asignado. Desde el punto de vista de la seguridad, con RBAC se permite la personalización avanzada de permisos para limitar, en el caso de los usuarios, el acceso a datos confidenciales o la ejecución de comandos en el nivel del superusuario. Para obtener más información sobre los roles de usuario, consulte la documentación de RBAC de Kubernetes.

      En este paso, usará kubectl para asignar el rol predefinido edit al usuario sammy en el espacio de nombres default. En un entorno de producción, es posible que quiera usar roles personalizados o vinculaciones de estos roles.

      Conceder permisos

      En Kubernetes, conceder permisos implica asignar el rol deseado a un usuario. Asigne los permisos edit al usuario sammy en el espacio de nombres default usando el siguiente comando:

      • kubectl create rolebinding sammy-edit-role --clusterrole=edit --user=sammy --namespace=default

      Con esto, se mostrará un resultado similar al siguiente:

      Output

      rolebinding.rbac.authorization.k8s.io/sammy-edit-role created

      Analizaremos este comando en mayor detalle:

      • Con create rolebinding sammy-edit-role se crea un nueva vinculación de rol, en este caso llamada sammy-edit-role.
      • Con --clusterrole=edit se asigna el rol predefinido edit en un ámbito global (rol de clúster).
      • Con --user=sammy se especifica el usuario con el que se vinculará el rol.
      • Con --namespace=default se conceden al usuario los permisos del rol dentro del espacio de nombres especificado; en este caso, default.

      A continuación, verifique los permisos del usuario enumerando pods en el espacio de nombres default. Puede comprobar si la autorización de RBAC funciona como se espera si no se muestran errores.

      • kubectl --kubeconfig=/home/your_local_user/.kube/config-sammy auth can-i get pods

      Verá el siguiente resultado:

      Output

      yes

      Ahora que asignó permisos a sammy, en la siguiente sección puede practicar la revocación de esos permisos.

      Revocar permisos

      La revocación de permisos en Kubernetes se realiza eliminando la vinculación de rol del usuario.

      Para este tutorial, elimine el rol edit del usuario sammy ejecutando el siguiente comando:

      • kubectl delete rolebinding sammy-edit-role

      Verá el siguiente resultado:

      Output

      rolebinding.rbac.authorization.k8s.io "sammy-edit-role" deleted

      Verifique si los permisos del usuario se revocaron como se esperaba enumerando los pods del espacio de nombres default:

      • kubectl --kubeconfig=/home/localuser/.kube/config-sammy --namespace=default get pods

      Obtendrá el siguiente error:

      Output

      Error from server (Forbidden): pods is forbidden: User "sammy" cannot list resource "pods" in API group "" in the namespace "default"

      Esto muestra que se revocó la autorización.

      Desde el punto de vista de la seguridad, con el modelo de autorización de Kubernetes se brinda a los administradores la flexibilidad necesaria para cambiar los derechos de los usuarios a pedido, según sea necesario. Además, el control de acceso basado en roles no se limita a un usuario físico; también puede conceder y eliminar permisos a servicios de clústeres, como se explicará en la siguiente sección.

      Para obtener más información sobre la autorización de RBAC y la manera de crear roles personalizados, consulte la documentación oficial.

      Paso 3: Administrar permisos de aplicación con cuentas de servicio

      Como se mencionó en la sección anterior, los mecanismos de autorización de RBAC van más allá de los usuarios humanos. Los usuarios de clústeres no humanos, como las aplicaciones, los servicios y los procesos que se ejecutan dentro de pods, se autentican con el servidor de API usando lo que en Kubernetes se conoce como cuentas de servicio. Cuando se crea un pod dentro de un espacio de nombres, puede permitir que se utilice la cuenta de servicio default o definir una cuenta de servicio que elija. La capacidad de asignar cuentas de servicio individuales a aplicaciones y procesos brinda a los administradores la libertad de conceder o revocar permisos según sea necesario. Además, se considera una buena práctica de seguridad asignar cuentas de servicio específicas a aplicaciones críticas para la producción. Debido a que las cuentas de servicio (SA) se utilizan para la autenticación, y por lo tanto para realizar comprobaciones de autorización de RBAC, los administradores de clústeres pueden contener las amenazas de seguridad cambiando los derechos de acceso de las cuentas de servicio y aislando el proceso delictivo.

      Para ofrece una demostración de las SA, en este tutorial utilizaremos un servidor web Nginx como una aplicación de ejemplo.

      Antes de asignar una SA en particular a su aplicación, deberá crear dicha SA. Cree una nueva cuenta de servicio llamada nginx-sa en el espacio de nombres default:

      • kubectl create sa nginx-sa

      Obtendrá este resultado:

      Output

      serviceaccount/nginx-sa created

      Verifique que la cuenta de servicio se haya creado ejecutando lo siguiente:

      Esto le proporcionará una lista de sus cuentas de servicios:

      Output

      NAME SECRETS AGE default 1 22h nginx-sa 1 80s

      A continuación, asignará un rol a la cuenta de servicio nginx-sa. Para este ejemplo, conceda a nginx-sa los mismos permisos que al usuario sammy:

      • kubectl create rolebinding nginx-sa-edit
      • --clusterrole=edit
      • --serviceaccount=default:nginx-sa
      • --namespace=default

      Al ejecutar esto, obtendrá el siguiente resultado:

      Output

      rolebinding.rbac.authorization.k8s.io/nginx-sa-edit created

      En este comando se utiliza el mismo formato que para el usuario sammy, a excepción del indicador --serviceaccount=default:nginx-sa, donde asigna la cuenta de servicio nginx-sa en el espacio de nombres default.

      Compruebe que la vinculación del rol se haya realizado correctamente usando este comando:

      Esto generará el siguiente resultado:

      Output

      NAME AGE nginx-sa-edit 23s

      Una vez que haya confirmado que la vinculación del rol para la cuenta del servicio se configuró correctamente, podrá asignar la cuenta de servicio a una aplicación. Asignar una cuenta de servicio particular a una aplicación le permitirá administrar sus derechos de acceso en tiempo real y, por lo tanto, mejorar la seguridad del clúster.

      A los efectos de este tutorial, un pod de nginx servirá como la aplicación de muestra. Cree el nuevo pod y especifique la cuenta de servicio nginx-sa con el siguiente comando:

      • kubectl run nginx --image=nginx --port 80 --serviceaccount="nginx-sa"

      En la primera parte del comando se crea un nuevo pod ejecutando un servidor web nginx en el puerto :80, y en la última parte en --serviceaccount="nginx-sa" se indica que en este pod se debe usar la cuenta de usuarionginx-sa y no la SA default.

      Con esto, se mostrará un resultado similar al siguiente:

      Output

      deployment.apps/nginx created

      Verifique que en la nueva aplicación se utilice la cuenta de servicio usando kubectl describe:

      • kubectl describe deployment nginx

      Con esto se mostrará una descripción extensa de los parámetros de implementación. En la sección de Pod Template, verá un resultado similar al siguiente:

      Output

      ... Pod Template: Labels: run=nginx Service Account: nginx-sa ...

      En esta sección, creó la cuenta de servicio nginx-sa en el espacio de nombres default y la asignó al servidor web nginx. Ahora puede controlar los permisos de nginx en tiempo real cambiando sus roles según sea necesario. También puede agrupar aplicaciones asignando la misma cuenta de servicio a cada una y luego aplicar cambios en bloque a los permisos. Finalmente, puede aislar aplicaciones críticas asignándoles una cuenta de usuario única.

      En resumen, la idea de asignar roles a sus aplicaciones e implementaciones es ajustar los permisos. En los entornos de producción reales, es posible que tenga varias implementaciones que requieran diferentes permisos, que abarcan desde privilegios de solo lectura a privilegios administrativos completos. Con el uso del RBAC se le proporciona la flexibilidad necesaria para restringir el acceso al clúster según sea necesario.

      A continuación, establecerá los controladores de admisión para controlar los recursos y protegerse de los ataques de agotamiento de recursos.

      Paso 4: Configurar controladores de admisión

      Los controladores de admisión de Kubernetes son complementos opcionales que se compilan en el binario kube-apiserver para ampliar las opciones de seguridad. Los controladores de admisión interceptan solicitudes una vez que superan la fase de autenticación y autorización. Cuando interceptan una solicitud, los controladores de admisión ejecutan el código especificado justo antes de que esta se aplique.

      Si bien el resultado de una verificación de autenticación o autorización es un booleano con el que se permite o deniega la solicitud, los controladores de admisión pueden ser mucho más diversos. Los controladores de admisión pueden validar las solicitudes de la misma forma que la autenticación, pero también pueden mutar o cambiar las solicitudes y modificar los objetos antes de su admisión.

      En este paso, usará los controladores de admisión ResourceQuota y LimitRange para proteger su clúster mediante la mutación de las solicitudes que podrían contribuir a un ataque de agotamiento de recursos o denegación de servicio. Con el controlador de admisión ResourceQuota se permite a los administradores restringir los recursos informáticos y de almacenamiento, y la cantidad de cualquier objeto dentro de un espacio de nombres, mientras que el controlador de admisión LimitRange limita el número de recursos que los contenedores utilizan. Utilizar estos dos controladores de admisión juntos protegerá su clúster contra ataques que hacen que sus recursos no estén disponibles.

      Para demostrar cómo funciona ResourceQuota, implementará algunas restricciones en el espacio de nombres default. Comience creando un nuevo archivo de objeto ResourceQuota:

      • nano resource-quota-default.yaml

      Añada la siguiente definición de objeto para establecer restricciones para el consumo de recursos en el espacio de nombres default. Puede ajustar los valores según sea necesario dependiendo de los recursos físicos de sus nodos:

      resource-quota-default.yaml

      apiVersion: v1
      kind: ResourceQuota
      metadata:
        name: resource-quota-default
      spec:
        hard:
          pods: "2"
          requests.cpu: "500m"
          requests.memory: 1Gi
          limits.cpu: "1000m"
          limits.memory: 2Gi
          configmaps: "5"
          persistentvolumeclaims: "2"
          replicationcontrollers: "10"
          secrets: "3"
          services: "4"
          services.loadbalancers: "2"
      

      En esta definición se utiliza la palabra clave hard para establecer restricciones estrictas, como el número máximo de pods, configmaps, PersistentVolumeClaims, ReplicationControllers, secrets, services y loadbalancers. Con esto también se establecen restricciones en los recursos de proceso, como las siguientes:

      • requests.cpu fija el valor de CPU máximo de solicitudes en milliCPU o una milésima parte de un núcleo de CPU.
      • requests.memory fija el valor de memoria máximo de solicitudes en bytes.
      • limits.cpu fija el valor máximo de CPU de los límites en milliCPU.
      • limits.memory fija el valor máximo de memoria de los límites en bytes.

      Guarde el archivo y ciérrelo.

      Ahora, cree el objeto en el espacio de nombres ejecutando el siguiente comando:

      • kubectl create -f resource-quota-default.yaml --namespace=default

      Obtendrá el siguiente resultado:

      Output

      resourcequota/resource-quota-default created

      Tenga en cuenta que usará el indicador -f para marcar en Kubernetes la ubicación del archivo ResourceQuota y el indicador --namespace para especificar el espacio de nombres que se actualizará.

      Una vez creado el objeto, ResourceQuota estará activo. Puede verificar las cuotas del espacio de nombres default con describe quota:

      • kubectl describe quota --namespace=default

      El resultado será similar al siguiente, con los límites estrictos que estableció en el archivo resource-quota-default.yaml:

      Output

      Name: resource-quota-default Namespace: default Resource Used Hard -------- ---- ---- configmaps 0 5 limits.cpu 0 1 limits.memory 0 2Gi persistentvolumeclaims 0 2 pods 1 2 replicationcontrollers 0 10 requests.cpu 0 500m requests.memory 0 1Gi secrets 2 3 services 1 4 services.loadbalancers 0 2

      Las ResourceQuotas se expresan en unidades absolutas. Por ello, añadir nodos no aumentará automáticamente los valores definidos aquí. Si se añaden nodos, deberá editar manualmente los valores aquí para equilibrar los recursos. Las ResourceQuotas pueden modificarse tanta veces como lo necesite, pero no pueden eliminarse a menos que se quite todo el espacio de nombres.

      Si necesita modificar una ResourceQuota en particular, actualice el archivo .yaml correspondiente y aplique los cambios usando el siguiente comando:

      • kubectl apply -f resource-quota-default.yaml --namespace=default

      Para obtener más información sobre el controlador de admisión ResourceQuota, consulte la documentación oficial.

      Ahora que su ResourceQuota está establecido, procederá con la configuración del controlador de admisión LimitRange. Así como ResourceQuota impone límites para los espacios de nombres, de forma similar LimitRange aplica las limitaciones declaradas validando y mutando contenedores.

      Siguiendo un procedimiento similar al que usó antes, comience creando el archivo objeto:

      • nano limit-range-default.yaml

      Ahora, puede usar el objeto LimitRange para restringir el uso de recursos según sea necesario. Añada el siguiente contenido como ejemplo de un caso de uso típico:

      limit-ranges-default.yaml

      apiVersion: v1
      kind: LimitRange
      metadata:
        name: limit-range-default
      spec:
        limits:
        - max:
            cpu: "400m"
            memory: "1Gi"
          min:
            cpu: "100m"
            memory: "100Mi"
          default:
            cpu: "250m"
            memory: "800Mi"
          defaultRequest:
            cpu: "150m"
            memory: "256Mi"
          type: Container
      

      Con los valores de ejemplo utilizados en limit-ranges-default.yaml se restringe la memoria del contenedor a un máximo de 1Gi y se limita el uso del CPU a un máximo de 400m, lo cual es una métrica de Kubernetes equivalente a 400 milliCPU. Esto implica que el uso del contenedor se limita a casi la mitad de su núcleo.

      A continuación, implemente el objeto en el servidor de API usando el siguiente comando:

      • kubectl create -f limit-range-default.yaml --namespace=default

      Esto generará el siguiente resultado:

      Output

      limitrange/limit-range-default created

      Ahora puede verificar los nuevos límites con el siguiente comando:

      • kubectl describe limits --namespace=default

      El resultado tendrá un aspecto similar a este:

      Output

      Name: limit-range-default Namespace: default Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio ---- -------- --- --- --------------- ------------- ----------------------- Container cpu 100m 400m 150m 250m - Container memory 100Mi 1Gi 256Mi 800Mi -

      Para ver LimitRanger en acción, implemente un contenedor estándar nginx con el siguiente comando:

      • kubectl run nginx --image=nginx --port=80 --restart=Never

      Esto generará el siguiente resultado:

      Output

      pod/nginx created

      Ejecutando el siguiente comando, compruebe la forma en que el controlador de admisión mutó el contenedor:

      • kubectl get pod nginx -o yaml

      Esto generará muchas líneas de resultado. Busque en la sección de especificaciones de los contenedores para encontrar los límites de recursos especificados en el controlador de admisión LimitRange.

      Output

      ... spec: containers: - image: nginx imagePullPolicy: IfNotPresent name: nginx ports: - containerPort: 80 protocol: TCP resources: limits: cpu: 250m memory: 800Mi requests: cpu: 150m memory: 256Mi ...

      Esto sería lo mismo que declarar manualmente resources y requests en la especificación del contenedor.

      En este paso, usó los controladores de admisión ResourceQuota y LimitRange para protegerse de los ataques malintencionados contra los recursos de su clúster. Para obtener más información sobre el controlador de admisión LimitRange, consulte la documentación oficial.

      Conclusión

      A través de esta guía, configuró una plantilla básica de seguridad de Kubernetes. Con esto, se establecieron la autenticación y autorización de usuarios, los privilegios de aplicaciones y la protección de los recursos de clústeres. Combinando todas las sugerencias de este artículo, tendrá una base sólida para una implementación de un clúster de Kubernetes de producción. A partir de ahí, puede comenzar a reforzar aspectos individuales de su clúster dependiendo de su escenario.

      Si desea obtener más información sobre Kubernetes, consulte nuestra página de recursos de Kubernetes o complete nuestro curso de aprendizaje por cuenta propia de Kubernetes para desarrolladores “full-stack”.



      Source link

      Рекомендуемые шаги по обеспечению безопасности кластера DigitalOcean Kubernetes


      Автор выбрал организацию Open Sourcing Mental Illness Ltd для получения пожертвований в рамках программы Write for DOnations.

      Введение

      Kubernetes, платформа для управления контейнерами с открытым исходным кодом, повсеместно становится предпочитаемым решением для автоматизации, масштабирования и управления кластерами с высоким уровнем доступности. Благодаря растущей популярности данной платформы, безопасность Kubernetes становится все более актуальной.

      Учитывая составные части Kubernetes, а также разнообразие сценариев развертывания, защита Kubernetes иногда может быть сопряжена с трудностями. Поэтому цель этой статьи заключается в том, чтобы заложить прочную основу безопасности для кластера DigitalOcean Kubernetes (DOKS). Необходимо отметить, что настоящий обучающий модуль охватывает базовые меры безопасности для Kubernetes и служит отправной точкой, а не исчерпывающим руководством. Дополнительные шаги можно найти в официальной документации Kubernetes.

      В этом руководстве вы будете выполнять основные шаги для защиты кластера DigitalOcean Kubernetes. Вы настроите защищенную локальную аутентификацию с сертификатами TLS/SSL, предоставите разрешения локальным пользователям с помощью механизмов управления доступом на базе ролей (RBAC), предоставите разрешения приложениям Kubernetes и развертыванию со служебными учетными записями, а также установите ограничения ресурсов при помощи контроллеров допуска ResourceQuota и LimitRange.

      Предварительные требования

      Чтобы выполнить это руководство, вам потребуется следующее:

      • Управляемый кластер DigitalOcean Kubernetes (DOKS) с тремя стандартными узлами, имеющими не менее чем 2 Гбайт ОЗУ и 1 виртуального процессора каждый. Подробные инструкции по созданию кластера DOKS можно найти в обучающем модуле Начало работы с Kubernetes. В этом обучающем руководстве используется версия DOKS 1.16.2-do.1.
      • Локальный клиент, настроенный для управления кластером DOKS, с файлом конфигурации кластера, загружаемым из панели управления DigitalOcean и сохраняемым как ~/.kube/config. Подробные инструкции по настройке удаленного управления DOKS можно найти в нашем руководстве «Подключение к кластеру DigitalOcean Kubernetes». В частности, вам потребуется следующее:
        • Интерфейс командной строки kubectl, установленный на локальном компьютере. Дополнительную информацию об установке и настройке kubectl можно найти в официальной документации. В этом обучающем руководстве будет использоваться версия kubectl 1.17.0-00.
        • Официальный инструмент командной строки DigitalOcean — doctl. Информацию о выполнении установки можно найти на странице doctl GitHub. В этом обучающем руководстве будет использоваться версия doctl 1.36.0.

      Шаг 1 — Активация аутентификации удаленного пользователя

      После завершения предварительных действий вы будете выполнять работу с одним суперпользователем Kubernetes, который аутентифицируется посредством заданного токена DigitalOcean. Однако обмен этими учетными данными не является эффективной практикой безопасности, поскольку данная учетная запись может вызвать масштабные и даже деструктивные изменения в вашем кластере. Чтобы снизить этот риск, вы можете настроить дополнительных пользователей, которые будут аутентифицироваться из соответствующих локальных клиентов.

      В этом разделе вы будете выполнять аутентификацию новых пользователей в удаленном кластере DOKS из локальных клиентов при помощи защищенных сертификатов SSL/TLS. Этот процесс будет проходить в три этапа: во-первых, вы создадите запросы подписи сертификатов (CSR) для каждого пользователя, а затем вы будете одобрять эти сертификаты непосредственно в кластере через kubectl. Наконец, вы создадите для каждого пользователя файл kubeconfig с соответствующими сертификатами. Расширенную информацию о дополнительных методах аутентификации при поддержке Kubernetes можно найти в документации по аутентификации Kubernetes.

      Создание запросов подписи сертификатов для новых пользователей

      Перед началом необходимо проверить подключение кластера DOKS к локальному компьютеру, настроенному с предварительными условиями:

      В зависимости от конфигурации, вы увидите примерно следующее:

      Output

      Kubernetes master is running at https://a6616782-5b7f-4381-9c0f-91d6004217c7.k8s.ondigitalocean.com CoreDNS is running at https://a6616782-5b7f-4381-9c0f-91d6004217c7.k8s.ondigitalocean.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

      Это означает, что вы подключены к кластеру DOKS.

      Далее создайте локальную папку для сертификатов клиента. Для целей настоящего руководства будет использоваться папка ~/certs для хранения всех сертификатов:

      В этом обучающем руководстве мы дадим новому пользователю с именем sammy доступ к кластеру. Вы можете изменить это на любого пользователя по вашему выбору. Используя библиотеку SSL и TLS OpenSSL, создайте новый закрытый ключ для вашего пользователя с помощью следующей команды:

      • openssl genrsa -out ~/certs/sammy.key 4096

      Флаг -out будет создавать выходной файл ~/certs/sammy.key, а 4096 устанавливает ключ длиной 4096 бит. Дополнительную информацию по OpenSSL можно найти в нашем руководстве по основам OpenSSL.

      Теперь создайте файл конфигурации запроса подписи сертификатов. Откройте следующий файл в текстовом редакторе (в этом обучающем модуле мы будем использовать nano):

      • nano ~/certs/sammy.csr.cnf

      Добавьте в файл sammy.csr.cnf следующее содержимое, чтобы задать в строке темы желаемое имя пользователя как обычное имя (CN) и группу в качестве организации (O):

      ~/certs/sammy.csr.cnf

      [ req ]
      default_bits = 2048
      prompt = no
      default_md = sha256
      distinguished_name = dn
      [ dn ]
      CN = sammy
      O = developers
      [ v3_ext ]
      authorityKeyIdentifier=keyid,issuer:always
      basicConstraints=CA:FALSE
      keyUsage=keyEncipherment,dataEncipherment
      extendedKeyUsage=serverAuth,clientAuth
      

      Файл конфигурации запроса подписи сертификатов содержит всю необходимую информацию, идентификационные данные пользователя и соответствующие параметры использования для данного пользователя. Последний аргумент extendedKeyUsage=serverAuth,clientAuth позволит пользователям аутентифицировать локальных клиентов с кластером DOKS при помощи сертификата после его подписания.

      Далее создайте запрос подписи сертификатов пользователя sammy:

      • openssl req -config ~/certs/sammy.csr.cnf -new -key ~/certs/sammy.key -nodes -out ~/certs/sammy.csr

      Параметр -config позволяет задать файл конфигурации для CSR, а сигналы -new, которые вы создаете — для нового CSR для ключа, указанного в -key.

      Вы можете проверить запрос подписи сертификатов посредством следующей команды:

      • openssl req -in ~/certs/sammy.csr -noout -text

      Здесь вы передаете в CSR с -in и используете -text, чтобы распечатать запрос сертификата в текстовом сообщении.

      В результатах будет показан запрос сертификатов, начало которого будет выглядеть следующим образом:

      Output

      Certificate Request: Data: Version: 1 (0x0) Subject: CN = sammy, O = developers Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public-Key: (4096 bit) ...

      Повторите эту же процедуру для создания CSR для всех дополнительных пользователей. Когда все запросы подписи сертификатов будут сохранены в папке администратора ~/certs, выполните следующий шаг для их утверждения.

      Управление запросами подписи сертификатов с API Kubernetes

      Вы можете одобрять или отклонять сертификаты TLS, выданные для API Kubernetes, используя инструмент командной строки kubectl. Это дает возможность убедиться, что запрошенный доступ соответствует данному пользователю. В этом разделе мы направим запрос сертификатов для пользователя sammy и одобрим его.

      Чтобы направить CSR в кластер DOKS, используйте следующую команду:

      cat <<EOF | kubectl apply -f -
      apiVersion: certificates.k8s.io/v1beta1
      kind: CertificateSigningRequest
      metadata:
        name: sammy-authentication
      spec:
        groups:
        - system:authenticated
        request: $(cat ~/certs/sammy.csr | base64 | tr -d 'n')
        usages:
        - digital signature
        - key encipherment
        - server auth
        - client auth
      EOF
      

      Используя heredoc-синтаксис в Bash, эта команда использует cat для передачи запроса сертификатов в команду kubectl apply.

      Рассмотрим запрос сертификатов более подробно:

      • name: sammy-authentication создает идентификатор метаданных, в данном случае с именем sammy-authentication.
      • request: $(cat ~/certs/sammy.csr | bar64 | tr -d 'n' направляет запрос подписи сертификатов sammy.csr в кластер, кодифицированный как base64.
      • В server auth и client auth указывается предполагаемое использование сертификата. В данном случае цель — аутентификация пользователя.

      Результат будет выглядеть примерно следующим образом:

      Output

      certificatesigningrequest.certificates.k8s.io/sammy-authentication created

      Вы можете проверить состояние запроса подписи сертификатов с помощью команды:

      В зависимости от конфигурации кластера, вы увидите примерно следующее:

      Output

      NAME AGE REQUESTOR CONDITION sammy-authentication 37s your_DO_email Pending

      Далее одобрите CSR с помощью команды:

      • kubectl certificate approve sammy-authentication

      Вы получите сообщение, подтверждающее операцию:

      Output

      certificatesigningrequest.certificates.k8s.io/sammy-authentication approved

      Примечание: Как администратор, вы также можете отклонить CSR посредством команды ​​​kubectl certificate deny sammy-authentication. Дополнительную информацию по управлению сертификатами TLS можно найти в официальной документации Kubernetes.

      После утверждения CSR вы можете загрузить его на локальный компьютер с помощью следующей команды:

      • kubectl get csr sammy-authentication -o jsonpath='{.status.certificate}' | base64 --decode > ~/certs/sammy.crt

      Эта команда декодирует сертификат Base64 для надлежащего использования с помощью kubectl, а затем сохраняет его как ~/certs/sammy.crt.

      С подписанным сертификатом sammy вы можете создавать пользовательский файл kubeconfig.

      Создание удаленных пользователей Kubeconfig

      Далее вы создадите специальный файл kubeconfig для пользователя sammy. Это позволит лучше контролировать возможность доступа пользователя к вашему кластеру.

      Первый шаг в создании нового kubeconfig — создание копии текущего файла kubeconfig. Для целей настоящего руководства новый файл kubeconfig будет иметь имя config-sammy:

      • cp ~/.kube/config ~/.kube/config-sammy

      Далее, измените новый файл:

      • nano ~/.kube/config-sammy

      Сохраните первые восемь строк этого файла, поскольку они содержат необходимую информацию для подключения SSL/TLS к кластеру. Далее, начиная с параметра user, замените текст следующими выделенными строками, чтобы файл выглядел примерно следующим образом:

      config-sammy

      apiVersion: v1
      clusters:
      - cluster:
          certificate-authority-data: certificate_data
        name: do-nyc1-do-cluster
      contexts:
      - context:
          cluster: do-nyc1-do-cluster
          user: sammy
        name: do-nyc1-do-cluster
      current-context: do-nyc1-do-cluster
      kind: Config
      preferences: {}
      users:
      - name: sammy
        user:
          client-certificate: /home/your_local_user/certs/sammy.crt
          client-key: /home/your_local_user/certs/sammy.key
      

      Примечание: для client-certificate и client-key используйте абсолютный путь к соответствующему местоположению их сертификатов. В противном случае kubectl выдаст ошибку.

      Сохраните и закройте файл.

      Вы можете протестировать новое подключение пользователя с помощью kubectl cluster-info:

      • kubectl --kubeconfig=/home/your_local_user/.kube/config-sammy cluster-info

      Ошибка будет выглядеть примерно так:

      Output

      To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. Error from server (Forbidden): services is forbidden: User "sammy" cannot list resource "services" in API group "" in the namespace "kube-system"

      Эта ошибка ожидается, поскольку пользователь sammy еще не имеет разрешения на запись каких-либо ресурсов в кластер. На следующем шаге будет рассмотрено предоставление разрешений пользователям. В результате вы увидите, что подключение SSL/TLS прошло успешно, а данные аутентификации sammy приняты в Kubernetes API.

      Шаг 2 — Авторизация пользователй через систему контроля доступа на основе ролей (RBAC)

      После аутентификации пользователя API определяет свои разрешения с помощью встроенной модели Kubernetes по контролю доступа на основе ролей (RBAC) RBAC — эффективный способ ограничения прав пользователя на основании его роли. С точки зрения безопасности, RBAC разрешает устанавливать детальные разрешения, чтобы ограничить возможность доступа пользователей к чувствительным данным или выполнения команд уровня суперпользователя. Более подробная информация о ролях пользователей содержится в документации Kubernetes RBAC.

      На этом шаге вы будете использовать kubectl для назначения заранее определенной роли edit пользователю sammy в области имен default. В производственной среде можно использовать настраиваемые роли и/или привязки ролей.

      Предоставление разрешений

      В Kubernetes предоставление разрешений означает назначение требуемой роли пользователю. Назначьте разрешения edit пользователю sammy в пространстве имен default с помощью следующей команды:

      • kubectl create rolebinding sammy-edit-role --clusterrole=edit --user=sammy --namespace=default

      Результат будет выглядеть примерно следующим образом:

      Output

      rolebinding.rbac.authorization.k8s.io/sammy-edit-role created

      Рассмотрим эту команду более подробно:

      • create rolebinding sammy-edit-role создает новую привязку ролей, в данном случае с именем sammy-edit-role.
      • --clusterrole=edit назначает заранее определенную роль edit в глобальном масштабе (роль кластера).
      • --user=sammy указывает, к какому пользователю следует привязать роль.
      • --namespace=default предоставляет пользователю разрешения ролей в пределах указанного пространства имен, в данном случае default.

      Далее проверьте разрешения пользователя посредством указания подов в пространстве имен default. Если ошибки не отображаются, то это означает, что авторизация RBAC работает так, как ожидается.

      • kubectl --kubeconfig=/home/your_local_user/.kube/config-sammy auth can-i get pods

      Результат будет выглядеть следующим образом:

      Output

      yes

      Теперь, когда вы назначили разрешения пользователю sammy, вы можете (в качестве упражнения) отозвать эти разрешения в следующем разделе.

      Отзыв разрешений

      Чтобы отозвать разрешения в Kubernetes, необходимо удалить привязку ролей пользователя.

      В этом обучающем модуле мы удалим роль edit пользователя sammy посредством следующей команды:

      • kubectl delete rolebinding sammy-edit-role

      Результат будет выглядеть следующим образом:

      Output

      rolebinding.rbac.authorization.k8s.io "sammy-edit-role" deleted

      Убедитесь, что разрешения пользователя были правильно отозваны посредством указания подов в пространстве имен default:

      • kubectl --kubeconfig=/home/localuser/.kube/config-sammy --namespace=default get pods

      Вы получите следующую ошибку:

      Output

      Error from server (Forbidden): pods is forbidden: User "sammy" cannot list resource "pods" in API group "" in the namespace "default"

      Это показывает, что авторизация отозвана.

      С точки зрения безопасности, модель авторизации Kubernetes дает администраторам кластеров возможность изменять права пользователей по запросу, по мере необходимости. Более того, контроль доступа на основе ролей не ограничивается физическим пользователем; вы можете также предоставлять разрешения кластерным службам и удалять их — это будет рассмотрено в следующем разделе.

      Дополнительную информацию об авторизации RBAC и создании настраиваемых ролей можно найти в официальной документации.

      Шаг 3 — Управление разрешениями приложений со служебными учетными записями

      Как упоминалось в предыдущем разделе, механизмы авторизации RBAC распространяются не только на физических пользователей. Виртуальные пользователи кластера — например, приложения, службы и процессы, запущенные внутри подов — аутентифицируются на сервере API при помощи того, что в Kubernetes называется «служебные учетные записи». Когда под создается в пространстве имен, вы можете либо позволить ему использовать служебную учетную запись default, либо определить служебную учетную запись по своему выбору. Благодаря способности назначать отдельные служебные учетные записи приложениям и процессам администраторы получают возможность предоставлять или отзывать разрешения, по мере необходимости. Кроме того, назначение конкретных служебных учетных записей для приложений, критичных для производства, считается наилучшей практикой безопасности. Поскольку учетные записи используются для аутентификации и, т.о., для проверки авторизации RBAC, администраторы кластеров могут устранять угрозы безопасности посредством изменения прав доступа к служебным учетным записям и изоляции процесса, угрожающего безопасности.

      Чтобы продемонстрировать служебные учетные записи, в этом обучающем руководстве будет использоваться веб-сервер Nginx как образец приложения.

      Перед назначением конкретной служебной учетной записи для вашего приложения ее необходимо создать. Создайте новую учетную запись с именем nginx-sa в пространстве имен default:

      • kubectl create sa nginx-sa

      Вы получите следующее:

      Output

      serviceaccount/nginx-sa created

      Убедитесь, что служебная учетная запись создана посредством следующей команды:

      В результате вы получите список ваших служебных учетных записей:

      Output

      NAME SECRETS AGE default 1 22h nginx-sa 1 80s

      Теперь вы назначите эту роль для служебной учетной записи nginx-sa. В данном примере необходимо предоставить для nginx-sa те же разрешения, что и для пользователя sammy:

      • kubectl create rolebinding nginx-sa-edit
      • --clusterrole=edit
      • --serviceaccount=default:nginx-sa
      • --namespace=default

      В результате будет получено следующее:

      Output

      rolebinding.rbac.authorization.k8s.io/nginx-sa-edit created

      Эта команда использует такой же формат, как для пользователя sammy, кроме флага --serviceaccount=default:nginx-sa, где вы назначаете служебную учетную запись nginx-sa в области имен default.

      Убедитесь, что привязка ролей успешно выполнена при помощи этой команды:

      Результат будет выглядеть следующим образом:

      Output

      NAME AGE nginx-sa-edit 23s

      Убедившись, что привязка роли к служебной учетной записи успешно настроена, вы можете назначить служебную учетную запись для приложения. Назначение определенной служебной учетной записи для приложения позволит управлять ее правами доступа в реальном времени и, таким образом, повысить безопасность кластеров.

      В этом обучающем модуле в качестве образца приложения мы будем использовать под nginx. Создайте новый под и укажите служебную учетную запись nginx-sa с помощью следующей команды:

      • kubectl run nginx --image=nginx --port 80 --serviceaccount="nginx-sa"

      Первая часть команды создает новый под на веб-сервере nginx на порту :80, а последняя часть --serviceaccount="nginx-sa" "nginx-sa" показывает, что данный под должен использовать служебную учетную запись nginx-sa, а не служебную учетную запись default.

      Результат будет выглядеть примерно следующим образом:

      Output

      deployment.apps/nginx created

      Убедитесь, что новое приложение использует служебную учетную запись с помощью kubectl describe:

      • kubectl describe deployment nginx

      В результате будет выведено подробное описание параметров развертывания. В разделе Pod Template вы увидите примерно следующее:

      Output

      ... Pod Template: Labels: run=nginx Service Account: nginx-sa ...

      В этом разделе мы создали служебную учетную запись nginx-sa в пространстве имен default и назначили ее для веб-сервера nginx. Теперь вы можете контролировать разрешения nginx в реальном времени посредством изменения его роли, по мере необходимости. Также вы можете группировать приложения посредством назначения одной служебной учетной записи для каждого из них и последующего внесения массовых изменений в разрешения. Наконец, вы можете изолировать критические приложения посредством назначения для них уникальной служебной учетной записи.

      В целом, смысл назначения ролей для ваших приложений/развертываний состоит в отладке разрешений. В реальной производственной среде может быть установлено несколько развертываний, требующих различных разрешений — от «только для чтения» до полных административных прав. Использование RBAC обеспечивает гибкость для ограничения доступа к кластеру, по мере необходимости.

      Далее вы настроите контроллеры допуска для контроля ресурсов и защиты от атак, вызывающих нехватку ресурсов.

      Шаг 4 — Настройка контроллеров допуска

      Контроллеры допуска Kubernetes — это опциональные плагины, компилируемые в двоичный файл kube-apiserver для расширения возможностей безопасности. Контроллеры допуска перехватывают запросы после прохождения этапа аутентификации и авторизации. После перехвата запроса контроллеры допуска выполняют указанный код непосредственно перед применением запроса.

      Результаты проверки аутентификации или разрешения являются булевыми величинами, разрешающими или отклоняющими запрос, а контроллеры допуска могут быть значительно более разнообразными. Контроллеры допуска могут подтверждать запросы таким же образом, как аутентификация, но также могут изменять запросы и объекты перед их допуском.

      На данном шаге мы будем использовать контроллеры допуска ResourceQuota и LimitRange допуска для защиты кластера посредством изменения запросов, которые могут способствовать возникновению нехватки ресурсов или DoS-атак. Контроллер допуска ResourceQuota позволяет администраторам ограничивать вычислительные ресурсы, ресурсы хранения, а также количество любых объектов в пространстве имен, а контроллер допуска LimitRange ограничивает количество ресурсов, используемых контейнерами. При совместном использовании этих двух контроллеров допуска ваш кластер будет защищен от атак, приводящих к недоступности ресурсов.

      Чтобы продемонстрировать работу ResourceQuota, зададим несколько ограничений в пространстве имен default. Начнем с создания нового файла объекта ResourceQuota:

      • nano resource-quota-default.yaml

      Добавьте следующее определение объекта для определения ограничений использования ресурсов в пространстве имен default. Вы можете изменить значения, если потребуется, в зависимости от физических ресурсов ваших узлов:

      resource-quota-default.yaml

      apiVersion: v1
      kind: ResourceQuota
      metadata:
        name: resource-quota-default
      spec:
        hard:
          pods: "2"
          requests.cpu: "500m"
          requests.memory: 1Gi
          limits.cpu: "1000m"
          limits.memory: 2Gi
          configmaps: "5"
          persistentvolumeclaims: "2"
          replicationcontrollers: "10"
          secrets: "3"
          services: "4"
          services.loadbalancers: "2"
      

      В этом определении используется ключевое слово hard для определения жестких ограничений — например, максимальное количество подов, карт configmaps, объектов PersistentVolumeClaims, контроллеров ReplicationControllers, объектов secrets, сервисов services и типов loadbalancers. В результате также устанавливаются ограничения вычислительных ресурсов:

      • requests.cpu, устанавливающий максимальное значение ЦП запросов в milliCPU, или одну тысячную ядра ЦП.
      • requests.memory, устанавливающий максимальное значение памяти запросов в байтах.
      • limits.cpu, устанавливающий максимальное значение ЦП предельных значений в milliCPU.
      • limits.memory, устанавливающий максимальное значение памяти предельных значений в байтах.

      Сохраните и закройте файл.

      Теперь создайте объект в пространстве имен при помощи следующей команды:

      • kubectl create -f resource-quota-default.yaml --namespace=default

      В результате вы получите следующее:

      Output

      resourcequota/resource-quota-default created

      Обратите внимание, что вы используете флаг -f для указания в Kubernetes места расположения файла ResourceQuota и флаг --namespace для указания, в каком пространстве будет обновлено пространство имен.

      После создания объекта ваш файл ResourceQuota будет активен. Вы можете проверить квоты пространств имен default с помощью describe quota:

      • kubectl describe quota --namespace=default

      Результат будет выглядеть примерно так, с жесткими ограничениями, которые вы задали в файле resource-quota-default.yaml:

      Output

      Name: resource-quota-default Namespace: default Resource Used Hard -------- ---- ---- configmaps 0 5 limits.cpu 0 1 limits.memory 0 2Gi persistentvolumeclaims 0 2 pods 1 2 replicationcontrollers 0 10 requests.cpu 0 500m requests.memory 0 1Gi secrets 2 3 services 1 4 services.loadbalancers 0 2

      ResourceQuotas выражаются в абсолютных единицах, поэтому добавление дополнительных узлов не будет автоматически увеличивать значения, определенные здесь. При добавлении дополнительных узлов вам потребуется вручную изменить эти значения для пропорционального распределения ресурсов. ResourceQuotas могут изменяться так часто, как вам потребуется, но они не могут удаляться, за исключением случаев, когда удаляется все пространство имен.

      Если вам потребуется изменить определенный файл ResourceQuota, обновите соответствующий файл .yaml и примените изменения с помощью следующей команды:

      • kubectl apply -f resource-quota-default.yaml --namespace=default

      Дополнительную информацию по контроллеру допуска ResourceQuota можно найти в официальной документации.

      После настройки вашего ResourceQuota вы перейдете к настройке контроллера допуска LimitRange. Аналогично тому, как ResourceQuota ограничивает пространства имен, LimitRange обеспечивает ограничения, заданные посредством подтверждения контейнеров и их изменения.

      Как и ранее, необходимо начать с создания файла объекта:

      • nano limit-range-default.yaml

      Теперь вы можете использовать объект LimitRange для ограничения использования ресурсов, по мере необходимости. Добавьте следующее содержимое в качестве образца типичного случая использования:

      limit-ranges-default.yaml

      apiVersion: v1
      kind: LimitRange
      metadata:
        name: limit-range-default
      spec:
        limits:
        - max:
            cpu: "400m"
            memory: "1Gi"
          min:
            cpu: "100m"
            memory: "100Mi"
          default:
            cpu: "250m"
            memory: "800Mi"
          defaultRequest:
            cpu: "150m"
            memory: "256Mi"
          type: Container
      

      Шаблонные значения, используемые в limit-ranges-default.yaml , ограничивают память контейнера максимальным значением 1Gi и ограничивают загрузку ЦП максимальным значением 400m — метрический эквивалент 400 milliCPU в Kubernetes, что означает, что контейнер ограничен использованием почти половины его ядра.

      Далее, разверните объект для сервера API с помощью следующей команды:

      • kubectl create -f limit-range-default.yaml --namespace=default

      Результат будет выглядеть следующим образом:

      Output

      limitrange/limit-range-default created

      Теперь вы можете проверить новые пределы с помощью следующей команды:

      • kubectl describe limits --namespace=default

      Результат будет выглядеть примерно следующим образом:

      Output

      Name: limit-range-default Namespace: default Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio ---- -------- --- --- --------------- ------------- ----------------------- Container cpu 100m 400m 150m 250m - Container memory 100Mi 1Gi 256Mi 800Mi -

      Чтобы увидеть LimitRanger в действии, разверните стандартный контейнер nginx с помощью следующей команды:

      • kubectl run nginx --image=nginx --port=80 --restart=Never

      Результат будет выглядеть следующим образом:

      Output

      pod/nginx created

      Проверьте, как контроллер допуска изменил контейнер, с помощью следующей команды:

      • kubectl get pod nginx -o yaml

      В результате будет получено много строк результатов. Посмотрите раздел спецификаций контейнера, чтобы узнать ограничения ресурсов, указанные контроллером допуска LimitRange:

      Output

      ... spec: containers: - image: nginx imagePullPolicy: IfNotPresent name: nginx ports: - containerPort: 80 protocol: TCP resources: limits: cpu: 250m memory: 800Mi requests: cpu: 150m memory: 256Mi ...

      Это будет выглядеть примерно так, как если бы вы декларировали вручную ресурсы и запросы в спецификации контейнера.

      На данном шаге вы использовали контроллеры допуска ResourceQuota и LimitRange для защиты от нападений злоумышленников на ресурсы вашего кластера. Дополнительную информацию по контроллеру допуска LimitRange можно найти в официальной документации.

      Заключение

      В этом руководстве вы настроили базовый шаблон безопасности Kubernetes. В результате были установлены: аутентификация и авторизация, привилегии приложений и защита кластерных ресурсов. Все предложения, рассмотренные в этом модуле, дадут вам твердую основу для развертывания производственного кластера Kubernetes. Теперь вы можете начинать проработку отдельных аспектов вашего кластера, в зависимости от вашего сценария.

      Если вы хотите узнать больше о Kubernetes, ознакомьтесь с нашей страницей ресурсов Kubernetes или посмотрите наш самостоятельный обучающий модуль «Kubernetes для разработчиков широкого профиля».



      Source link

      Información sobre generadores en JavaScript


      El autor seleccionó a Open Internet/Free Speech Fund para recibir una donación como parte del programa Write for DOnations.

      Introducción

      En ECMAScript 2015, se introdujeron generadores en el lenguaje de JavaScript. Un generador es un proceso que puede pausarse, reanudarse y producir varios valores. Un generador en JavaScript consta de una función generadora que muestra un objeto iterable Generator.

      Los generadores pueden mantener el estado, y proporcionar con ello una forma eficiente de crear iteradores, y encargarse de flujos de datos infinitos que se pueden emplear para implementar desplazamiento infinito en una página en el frontend de una aplicación web, para operar con datos de ondas de sonido, y más. Además, cuando se usan con promesas, los generadores pueden imitar la funcionalidad de async/await, lo que nos permite abordar el código asíncrono de una manera más sencilla y legible. Aunque async/await es una alternativa más frecuente para abordar los casos de uso asíncrono más comunes y sencillos, como la obtención de datos de una API, en los generadores se incluyen funciones más avanzadas que hacen que valga la pena aprender a usarlos.

      En este artículo, veremos la forma de crear funciones generadoras e iterar objetos Generator, la diferencia entre yield y return dentro de un generador y otros aspectos vinculados al trabajo con generadores.

      Funciones generadoras

      Una función generadora es una función que muestra un objeto Generator y se define con la palabra clave function seguida de un asterisco (*), como se muestra a continuación:

      // Generator function declaration
      function* generatorFunction() {}
      

      De vez en cuando, verá el asterisco junto al nombre de la función, a diferencia de la palabra clave de la función; por ejemplo, function *generatorFunction(). Esto funciona igual, aunque la de function* es una sintaxis más aceptada.

      Las funciones generadoras también pueden definirse en una expresión, como las funciones regulares:

      // Generator function expression
      const generatorFunction = function*() {}
      

      Los generadores pueden ser, incluso, los métodos de un objeto o una clase:

      // Generator as the method of an object
      const generatorObj = {
        *generatorMethod() {},
      }
      
      // Generator as the method of a class
      class GeneratorClass {
        *generatorMethod() {}
      }
      

      En los ejemplos de este artículo se usará la sintaxis de declaración de las funciones generadoras.

      Nota: A diferencia de las funciones regulares, los generadores no se pueden construir a partir de la palabra clave new ni pueden ser utilizarse junto con las funciones de flecha.

      Ahora que sabe declarar funciones generadoras, veremos los objetos Generator iterables que estas muestran.

      Objetos Generator

      Tradicionalmente, las funciones en JavaScript se ejecutan hasta completarse y la invocación de una muestra un valor cuando se alcanza la palabra clave return. Si se omite la palabra clave return, en una función se mostrará implícitamente undefined.

      En el siguiente código, por ejemplo, declaramos una función sum() que muestra un valor que es la suma de dos argumentos enteros:

      // A regular function that sums two values
      function sum(a, b) {
        return a + b
      }
      

      Al invocar la función se muestra un valor que es la suma de los argumentos:

      const value = sum(5, 6) // 11
      

      Sin embargo, una función generadora no muestra un valor de inmediato y como alternativa se muestra un objeto Generator iterable. En el siguiente ejemplo, se declara una función y se le asigna un valor de devolución único, como en una función estándar:

      // Declare a generator function with a single return value
      function* generatorFunction() {
        return 'Hello, Generator!'
      }
      

      Cuando invoquemos la función generadora, mostrará el objeto Generator, que podemos asignar a una variable:

      // Assign the Generator object to generator
      const generator = generatorFunction()
      

      Si esta fuera una función regular, esperaríamos que generator nos proporcionara la cadena mostrada en la función. Sin embargo, lo que realmente obtenemos es un objeto en estado suspended. Al invocar generator, por lo tanto, obtendremos un resultado similar al siguiente:

      Output

      generatorFunction {<suspended>} __proto__: Generator [[GeneratorLocation]]: VM272:1 [[GeneratorStatus]]: "suspended" [[GeneratorFunction]]: ƒ* generatorFunction() [[GeneratorReceiver]]: Window [[Scopes]]: Scopes[3]

      El objeto Generator mostrado por la función es un iterador. Un iterador es un objeto que tiene un método next() disponible, el cual se utiliza para iterar una secuencia de valores. El método next() muestra un objeto con propiedades value y done. value representa el valor mostrado y done indica si el iterador recorrió o no todos sus valores.

      Conociendo esto, invocaremos next() en nuestro generator y obtendremos el valor y el estado actual del iterador:

      // Call the next method on the Generator object
      generator.next()
      

      Esto generará el siguiente resultado:

      Output

      {value: "Hello, Generator!", done: true}

      El valor mostrado después de invocar next() es Hello, Generator!, y el estado de done es true, ya que este valor provino de un return que cerró el iterador. Debido a que la operación del iterador se completó, el estado de la función generadora pasará de suspended a closed. Invocar generator de nuevo dará el siguiente resultado:

      Output

      generatorFunction {<closed>}

      Hasta ahora, solo hemos demostrado que una función generadora puede ser una alternativa más compleja para obtener el valor return de una función. Pero las funciones generadoras también tienen características únicas que las distinguen de las funciones normales. En la siguiente sección, aprenderá sobre el operador yield y veremos cómo se puede pausar y reanudar la ejecución de un generador.

      Operadores yield

      Con los generadores se introdujo una nueva palabra clave en JavaScript: yield. Con yield se puede pausar una función generadora y mostrar el valor que le sigue a yield, y así proporcionar una opción ligera para iterar valores.

      En este ejemplo, pausaremos la función generadora tres veces con diferentes valores y mostraremos un valor al final. Luego asignaremos nuestro objeto Generator a la variable generator.

      // Create a generator function with multiple yields
      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
      
        return 'The Oracle'
      }
      
      const generator = generatorFunction()
      

      Ahora, cuando invoquemos next() en la función generadora, se pausará cada vez que encuentre yield. Se fijará el valor false para done después de cada yield, lo cual indicará que el generador no ha terminado. Una vez que encuentre un return, o ya no se encuentren más yield en la función, done pasará a tener el valor true y el generador habrá terminado.

      Utilice el método next() cuatro veces seguidas:

      // Call next four times
      generator.next()
      generator.next()
      generator.next()
      generator.next()
      

      Estos darán las siguientes cuatro líneas de resultados en orden:

      Output

      {value: "Neo", done: false} {value: "Morpheus", done: false} {value: "Trinity", done: false} {value: "The Oracle", done: true}

      Tenga en cuenta que un generador no requiere un return; si se omite, la última iteración mostrará {value: undefined, done: true}, al igual que cualquier invocación posterior de next() después de que un generador haya finalizado.

      Iterar un generador

      Usando el método next(), iteramos manualmente el objeto Generator y recibimos todas las propiedades de value y done del objeto completo. Sin embargo, al igual que Array, Map y Set, un Generator sigue el protocolo de iteración y puede iterarse con for...of:

      // Iterate over Generator object
      for (const value of generator) {
        console.log(value)
      }
      

      Con esto, se mostrará lo siguiente:

      Output

      Neo Morpheus Trinity

      El operador de propagación también puede usarse para asignar los valores de un Generator en una matriz.

      // Create an array from the values of a Generator object
      const values = [...generator]
      
      console.log(values)
      

      Esto proporcionará la siguiente matriz:

      Output

      (3) ["Neo", "Morpheus", "Trinity"]

      Tanto con la propagación como con for...of, no se tomará en cuenta return en los valores (en este caso, habría sido 'The Oracle').

      Nota: Si bien ambos métodos son eficaces para trabajar con generadores finitos, si un generador se encarga de un flujo de datos infinito, no será posible usar directamente la propagación ni for...of sin crear un bucle infinito.

      Cerrar un generador

      Como vimos, en el caso de un generador se puede fijar el valor true para la propiedad done y el valor closed para su estado mediante la iteración de todos sus valores. Existen dos alternativas adicionales para cancelar inmediatamente un generador: el método return() y el método throw().

      Con return(), el proceso del generador se puede finalizar en cualquier punto, como si hubiera una declaración return en el cuerpo de la función. Puede pasar un argumento a return() o dejarlo en blanco para un valor indefinido.

      Para demostrar return(), se creará un generador con algunos valores yield pero sin return en la definición de la función:

      function* generatorFunction() {
        yield 'Neo'
        yield 'Morpheus'
        yield 'Trinity'
      }
      
      const generator = generatorFunction()
      

      El primer next() nos proporcionará 'Neo', con el valor false fijado para done. Si invocamos un método return() en el objeto Generator justo después de esto, obtendremos el valor pasado y se cambiará done a true. Cualquier invocación adicional de next() nos proporcionará la respuesta del generador completada predeterminada con un valor indefinido.

      Para demostrar esto, ejecute los siguientes tres métodos en generator:

      generator.next()
      generator.return('There is no spoon!')
      generator.next()
      

      Esto proporcionará los tres resultados siguientes:

      Output

      {value: "Neo", done: false} {value: "There is no spoon!", done: true} {value: undefined, done: true}

      El método return() forzó al objeto Generator a completarse e ignorar cualquier otra palabra clave de yield. Esto es particularmente útil en la programación asíncrona cuando es necesario hacer que las funciones puedan cancelarse; por ejemplo, mediante la interrupción de una solicitud web cuando un usuario quiere realizar una acción diferente, ya que no es posible cancelar directamente una promesa.

      Si en el cuerpo de una función generadora se incluye una alternativa para detectar y manejar los errores, puede usar el método throw() para generar un error en el generador. Con esto se inicia el generador, se genera un error y se finaliza el generador.

      Para demostrar esto, dispondremos un try...catch dentro del cuerpo de la función generadora y registraremos un error si se encuentra:

      // Define a generator function with a try...catch
      function* generatorFunction() {
        try {
          yield 'Neo'
          yield 'Morpheus'
        } catch (error) {
          console.log(error)
        }
      }
      
      // Invoke the generator and throw an error
      const generator = generatorFunction()
      

      Ahora, ejecutaremos el método next(), seguido de throw():

      generator.next()
      generator.throw(new Error('Agent Smith!'))
      

      Esto generará el siguiente resultado:

      Output

      {value: "Neo", done: false} Error: Agent Smith! {value: undefined, done: true}

      Mediante throw(), se introdujo en el generador un error detectado por try...catch y registrado en la consola.

      Métodos y estados del objeto generador

      En la siguiente tabla, se muestra una lista de métodos que pueden utilizarse en los objetos Generator:

      Método Descripción
      next() Muestra el valor siguiente en un generador.
      return() Muestra un valor en un generador y finaliza el generador.
      throw() Genera un error y finaliza el generador.

      En la siguiente tabla, se enumeran los posibles estados de un objeto Generator:

      Estado Descripción
      suspended La ejecución del generador se detuvo, pero el proceso de este no finalizó.
      closed El proceso del generador finalizó porque se produjo un error, un retorno o una iteración de todos los valores.

      Delegación de yield

      Además del operador regular yield, los generadores también pueden usar la expresión yield* para delegar más valores a otros generadores. Cuando se encuentre yield* dentro de un generador, se ubicará dentro del generador delegado y empezarán a iterarse todos los yield hasta que se cierre ese generador. Esto se puede utilizar para separar diferentes funciones generadoras con el fin de organizar su código de forma semántica y, al mismo tiempo, iterar todos los yield en el orden correcto.

      Para demostrar esto, podemos crear dos funciones generadoras, una de las cuales operará mediante yield* en la otra:

      // Generator function that will be delegated to
      function* delegate() {
        yield 3
        yield 4
      }
      
      // Outer generator function
      function* begin() {
        yield 1
        yield 2
        yield* delegate()
      }
      

      A continuación, iteraremos la función generadora begin():

      // Iterate through the outer generator
      const generator = begin()
      
      for (const value of generator) {
        console.log(value)
      }
      

      Esto dará los siguientes valores en el orden en que se generan:

      Output

      1 2 3 4

      El generador externo produjo los valores 1 y 2, y luego se delegó al otro generador con yield*, que mostró 3 y 4.

      yield* también puede hacer delegaciones a cualquier objeto que sea iterable, como una matriz o un mapa. La delegación de Yield puede ser útil para organizar código, ya que cualquier función de un generador que intente usar yield tendría que ser también un generador.

      Flujos de datos infinitos

      Uno de los aspectos útiles de los generadores es la capacidad de trabajar con flujos de datos infinitos y grupos. Esto puede demostrarse creando un bucle infinito dentro de una función generadora que incremente un número en una unidad.

      En el siguiente bloque de código, definimos esta función generadora y luego iniciamos el generador:

      // Define a generator function that increments by one
      function* incrementer() {
        let i = 0
      
        while (true) {
          yield i++
        }
      }
      
      // Initiate the generator
      const counter = incrementer()
      

      Ahora, itere los valores usando next():

      // Iterate through the values
      counter.next()
      counter.next()
      counter.next()
      counter.next()
      

      Esto generará el siguiente resultado:

      Output

      {value: 0, done: false} {value: 1, done: false} {value: 2, done: false} {value: 3, done: false}

      En la función se muestran los valores sucesivos en el bucle infinito mientras que el valor de la propiedad done se mantiene en false, lo cual garantiza que no finalizará.

      Con los generadores, no tiene que preocuparse por crear un bucle infinito, porque puede detener y reanudar la ejecución cuando lo desee. Sin embargo, de todos modos debe tener cuidado con la forma en la que invoca el generador. Si utiliza la propagación o for...of en un flujo de datos infinito, seguirá iterando un bucle infinito todo a la vez, lo cual hará que el entorno se bloquee.

      Para un ejemplo más complejo de un flujo de datos infinito, podemos crear una función generadora de Fibonacci. La secuencia de Fibonacci, que suma continuamente los dos valores anteriores, puede escribirse usando un bucle infinito en un generador, como se muestra a continuación:

      // Create a fibonacci generator function
      function* fibonacci() {
        let prev = 0
        let next = 1
      
        yield prev
        yield next
      
        // Add previous and next values and yield them forever
        while (true) {
          const newVal = next + prev
      
          yield newVal
      
          prev = next
          next = newVal
        }
      }
      

      Para probar esto, podemos usar un bucle un número finito de veces e imprimir la secuencia de Fibonacci en la consola.

      // Print the first 10 values of fibonacci
      const fib = fibonacci()
      
      for (let i = 0; i < 10; i++) {
        console.log(fib.next().value)
      }
      

      Esto dará el siguiente resultado:

      Output

      0 1 1 2 3 5 8 13 21 34

      La capacidad de trabajar con conjuntos de datos infinitos es una de las razones por la cuales los generadores son tan poderosos. Esto puede ser útil para ejemplos como el caso de la implementación de desplazamiento infinito en el frontend de una aplicación web.

      Pasar los valores en los generadores

      A lo largo de este artículo, usamos generadores como iteradores y produjimos valores en cada iteración. Además de producir valores, los generadores también pueden consumir valores de next(). En este caso, yield contendrá un valor.

      Es importante observar que con el primer next() que se invoqué no se pasará un valor, sino que solo se iniciará el generador. Para demostrar esto, podemos registrar el valor de yield e invocar next() unas cuantas veces con algunos valores.

      function* generatorFunction() {
        console.log(yield)
        console.log(yield)
      
        return 'The end'
      }
      
      const generator = generatorFunction()
      
      generator.next()
      generator.next(100)
      generator.next(200)
      

      Esto generará el siguiente resultado:

      Output

      100 200 {value: "The end", done: true}

      También es posible propagar el generador con un valor inicial. En el siguiente ejemplo, crearemos un bucle for y pasaremos cada valor al método next(), pero también pasaremos un argumento a la función inicial:

      function* generatorFunction(value) {
        while (true) {
          value = yield value * 10
        }
      }
      
      // Initiate a generator and seed it with an initial value
      const generator = generatorFunction(0)
      
      for (let i = 0; i < 5; i++) {
        console.log(generator.next(i).value)
      }
      

      Recuperaremos el valor de next() y produciremos un nuevo valor para la siguiente iteración, que es igual a diez veces el valor anterior. Esto dará el siguiente resultado:

      Output

      0 10 20 30 40

      Otra forma de abordar la iniciación de un generador es ajustarlo en una función que siempre invocará next() una vez antes de hacer cualquier otra acción.

      async y await con generadores

      Una función asíncrona es un tipo de función disponible en ES6+ de JavaScript que facilita el trabajo con datos asíncronos al hacer que parezcan síncronos. Los generadores tienen una matriz de capacidades más amplia que las funciones asíncronas, pero son capaces de replicar un comportamiento similar. Implementar de esta manera una programación asíncrona puede aumentar la flexibilidad de su código.

      En esta sección, mostraremos un ejemplo de la reproducción de async y await con generadores.

      Crearemos una función asíncrona que utiliza la API de Fetch para obtener datos de la API JSONPlaceholder (que proporciona datos JSON de ejemplo para pruebas) y registrar la respuesta en la consola.

      Comience definiendo una función asíncrona llamada getUsers, que obtiene datos de la API y muestra una matriz de objetos, y luego invoque getUsers:

      const getUsers = async function() {
        const response = await fetch('https://jsonplaceholder.typicode.com/users')
        const json = await response.json()
      
        return json
      }
      
      // Call the getUsers function and log the response
      getUsers().then(response => console.log(response))
      

      Esto proporcionará datos JSON similares a los siguientes:

      Output

      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      Usando generadores, podemos crear algo casi idéntico que no utilice las palabras claves async y await. En su lugar, se usarán una nueva función que creamos y los valores yield en vez de las promesas await.

      En el siguiente bloque de código, definimos una función llamada getUsers que utiliza nuestra nueva función asyncAlt (que escribiremos más adelante) para imitar a async y await.

      const getUsers = asyncAlt(function*() {
        const response = yield fetch('https://jsonplaceholder.typicode.com/users')
        const json = yield response.json()
      
        return json
      })
      
      // Invoking the function
      getUsers().then(response => console.log(response))
      

      Como podemos ver, es casi idéntica a la implementación de async y await, excepto que se pasa una función generadora que produce valores.

      Ahora podemos crear una función asyncAlt que se asemeje a una función asíncrona. asyncAlt cuenta con una función generadora como un parámetro, que es nuestra función productora de promesas que muestra fetch. asyncAlt muestra una función por sí misma y resuelve cada promesa que encuentra hasta la última:

      // Define a function named asyncAlt that takes a generator function as an argument
      function asyncAlt(generatorFunction) {
        // Return a function
        return function() {
          // Create and assign the generator object
          const generator = generatorFunction()
      
          // Define a function that accepts the next iteration of the generator
          function resolve(next) {
            // If the generator is closed and there are no more values to yield,
            // resolve the last value
            if (next.done) {
              return Promise.resolve(next.value)
            }
      
            // If there are still values to yield, they are promises and
            // must be resolved.
            return Promise.resolve(next.value).then(response => {
              return resolve(generator.next(response))
            })
          }
      
          // Begin resolving promises
          return resolve(generator.next())
        }
      }
      

      Esto dará el mismo resultado que la versión de async y await:

      Output

      [ {id: 1, name: "Leanne Graham" ...}, {id: 2, name: "Ervin Howell" ...}, {id: 3, name": "Clementine Bauch" ...}, {id: 4, name: "Patricia Lebsack"...}, {id: 5, name: "Chelsey Dietrich"...}, ...]

      Tenga en cuenta que esta implementación es para demostrar cómo se pueden usar los generadores en lugar de async y await, y que no es un diseño listo para la producción. No tiene configurada la gestión de errores ni tiene la capacidad de pasar los parámetros a los valores producidos. Aunque con este método se puede añadir flexibilidad a su código, a menudo async/await será una mejor opción, ya que abstrae los detalles de la implementación y le permite enfocarse en escribir código productivo.

      Conclusión

      Los generadores son procesos que cuya ejecución puede detenerse y reanudarse. Son una potente y versátil característica de JavaScript, aunque no se utilizan comúnmente. En este tutorial, aprendió acerca de las funciones generadoras y los objetos generadores, los métodos disponibles para los generadores, los operadores yield y yield* y los generadores utilizados con conjuntos de datos finitos e infinitos. También exploramos una manera de implementar código asíncrono sin devoluciones de llamada anidadas ni largas cadenas de promesas.

      Si desea aprender más sobre la sintaxis de JavaScript, consulte nuestros tutoriales Información sobre This, Bind, Call y Apply en JavaScript e Información sobre los objetos Map y Set en JavaScript.



      Source link