Desenvolvendo apps

Desenvolque uma configuração para implementar sua carga de trabalho do app para IBM Cloud® Kubernetes Service. Como o Kubernetes é uma plataforma de orquestração de contêiner extensível que não exige um idioma ou um app específico, é possível executar várias cargas de trabalho, como apps stateless, stateful e de processamento de dados que são gravados no idioma de sua escolha.

Especificando os requisitos de app em seu arquivo YAML

Em Kubernetes, você descreve seu app em um arquivo YAML que declara a configuração do objeto do Kubernetes. O servidor de API do Kubernetes, então, processa o arquivo YAML e armazena a configuração e o estado necessário do objeto no armazenamento de dados etcd. O planejador do Kubernetes planeja suas cargas de trabalho nos nós do trabalhador dentro de seu cluster, levando em consideração a especificação em seu arquivo YAML, quaisquer políticas de cluster que o administrador configura e a capacidade disponível do cluster.

Revise uma cópia do arquivo YAML completo. Em seguida, revise as seções a seguir para entender como é possível aprimorar a implementação do app.

Quer mais informações sobre como os objetos de Kubernetes trabalham juntos para a sua implantação? Confira Understanding Kubernetes objects para apps.

Metadados de implementação básica

Use a versão apropriada da API para o tipo de objeto do Kubernetes que você implementa. A versão da API determina os recursos suportados para o objeto do Kubernetes que estão disponíveis para você. O nome fornecido nos metadados é o nome do objeto, não seu rótulo. Você usa o nome ao interagir com o objeto, como kubectl get deployment <name>.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasliberty

Conjunto de réplicas

Para aumentar a disponibilidade de seu app, é possível especificar um conjunto de réplicas em sua implementação. Em um conjunto de réplicas, são definidas quantas instâncias de seu app você deseja implementar. Os conjuntos de réplicas são gerenciados e monitorados por sua implementação do Kubernetes. Se uma instância do app fica inativa, o Kubernetes acelera automaticamente uma nova instância de seu app para manter o número especificado de instâncias do app.

spec:
  replicas: 3

Rótulos

Com rótulos, é possível marcar diferentes tipos de recursos em seu cluster com o mesmo par key: value. Em seguida, é possível especificar o seletor para corresponder ao rótulo para que seja possível construir sobre esses outros recursos. Se você planeja expor seu app publicamente, deve-se usar um rótulo que corresponda ao seletor especificado no serviço. No exemplo, a especificação de implementação usa o template que corresponde ao rótulo app: wasliberty.

É possível recuperar objetos que são rotulados em seu cluster, como para ver os componentes staging ou production. Por exemplo, liste todos os recursos com um rótulo env: production em todos os namespaces no cluster. Nota: você precisa de acesso a todos os namespaces para executar esse comando.

kubectl get all -l env=production --all-namespaces
selector:
  matchLabels:
    app: wasliberty
template:
  metadata:
    labels:
      app: wasliberty

Afinidade

Especifique a afinidade (colocação conjunta) quando desejar ter mais controle sobre em quais nós de trabalho os pods serão agendados. A afinidade afeta os pods somente no tempo de planejamento. Por exemplo, para difundir a implementação em nós do trabalhador em vez de permitir que os pods sejam planejados no mesmo nó, use a opção podAntiAffinity com seus clusters padrão. É possível definir dois tipos de antiafinidade do pod: preferencial ou necessário.

Para obter mais informações, consulte a documentação do Kubernetes em Designando pods a nós.

Antiafinidade necessária
É possível implementar apenas o número de réplicas para as quais você tem nós do trabalhador. Por exemplo, se você tiver três nós do trabalhador em seu cluster, mas definir cinco réplicas em seu arquivo YAML, somente três réplicas serão implementadas. Cada réplica mora em um nó de trabalhador diferente. As duas réplicas restantes continuarão pendentes. Se você incluir outro nó do trabalhador no cluster, uma das réplicas restantes será implementada no novo nó do trabalhador automaticamente. Se um nó do trabalhador falhar, o pod não reagendará porque a política de afinidade é necessária. Para obter um YAML de exemplo com antiafinidade necessária, consulte App Liberty com antiafinidade de pod necessária.
Antiafinidade preferencial
É possível implementat seus pods em nós com capacidade disponível, o que proporciona mais flexibilidade para sua carga de trabalho. Quando possível, os pods são planejados em diferentes nós do trabalhador. Por exemplo, se você tiver três nós do trabalhador com capacidade suficiente em seu cluster, ele poderá planejar os cinco pods de réplica nos nós. No entanto, se você adicionar mais dois nós de trabalho ao seu cluster, a regra de afinidade não força os dois pods adicionais que estão em execução nos nós existentes a serem reprogramados para o nó disponível.
Afinidade do nó do trabalhador
É possível configurar sua implementação para executar apenas em determinados nós do trabalhador, como o bare metal. Para obter mais informações, veja Implementando apps em nós do trabalhador específicos usando rótulos.

Exemplo de antiafinidade preferencial

spec:
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - wasliberty
          topologyKey: kubernetes.io/hostname

Imagem do contêiner

Especifique a imagem que você deseja usar para seus contêineres, o local da imagem e a política de extração de imagem. Se você não especificar uma tag de imagem, por padrão, a imagem identificada como latest será extraída.

Evite usar a tag mais recente para cargas de trabalho de produção. Você pode não ter testado sua carga de trabalho com a imagem mais recente se estiver usando um repositório público ou compartilhado, como o Docker Hub ou o IBM Cloud Container Registry.

Por exemplo, para listar as tags de imagens públicas da IBM:

  1. Alterne para a região de registro global.
    ibmcloud cr region-set global
    
  2. Liste as imagens da IBM.
    ibmcloud cr images --include-ibm
    

O imagePullPolicy padrão é configurado como IfNotPresent, que fará pull da imagem somente se ela não existir localmente. Se desejar que a imagem seja puxada toda vez que o contêiner for iniciado, especifique o imagePullPolicy: Always.

containers:
- name: wasliberty
  image: icr.io/ibm/liberty:webProfile8
  imagePullPolicy: Always

Porta para o serviço do app

Selecione uma porta de contêiner na qual abrir os serviços do app. Para ver qual porta precisa ser aberta, consulte suas especificações de app ou o Dockerfile. A porta é acessível por meio da rede privada, mas não de uma conexão de rede pública. Para expor o app publicamente, deve-se criar um serviço NodePort, balanceador de carga ou Ingress. Você usa esse mesmo número da porta ao criar um objeto Service.

A porta 25 está bloqueada para todos os serviços no IBM Cloud.

ports:
- containerPort: 9080

Solicitações de recurso e limites

Os administradores de cluster certificam-se de que as equipes que compartilham um cluster não obtenham mais do que seu compartilhamento justo de recursos de cálculo (memória e CPU) criando um objeto ResourceQuota para cada namespace do Kubernetes no cluster. Se o administrador de cluster configurar uma cota de recurso de cálculo, cada contêiner dentro do modelo de implementação deverá especificar solicitações de recurso e limites para memória e CPU, caso contrário, a criação do pod falhará.

  1. Verifique se uma cota de recurso está configurada para um namespace.
    kubectl get quota --namespace=<namespace>
    
  2. Veja quais são os limites de cota.
    kubectl describe quota <quota_name> --namespace=<namespace>
    

Mesmo que nenhuma cota de recurso seja configurada, é possível incluir solicitações de recurso e limites em sua implementação para melhorar o gerenciamento de recursos do nó do trabalhador.

Se um contêiner exceder seu limite, o contêiner poderá ser reiniciado ou falhar. Se um contêiner exceder uma solicitação, seu pod poderá ser despejado se o nó do trabalhador ficar sem esse recurso que foi excedido. Para obter mais informações sobre resolução de problemas, consulte Os pods falham repetidamente ao reiniciar ou são removidos inesperadamente.

Solicitação
A quantidade mínima do recurso reservado pelo planejador para utilização do contêiner. Se a quantia for igual ao limite, a solicitação será garantida. Se a quantia for menor do que o limite, a solicitação ainda será garantida, mas o planejador poderá usar a diferença entre a solicitação e o limite para preencher os recursos de outros contêineres.
Limite
A quantidade máxima do recurso que o contêiner pode consumir. Se a quantia total de recursos que for usada nos contêineres exceder a quantia disponível no nó do trabalhador, os contêineres poderão ser despejados para liberar espaço. Para evitar o despejo, configure a solicitação de recurso igual ao limite do contêiner. Se nenhum limite for especificado, o padrão será a capacidade do nó do trabalhador.

Para obter mais informações, consulte a Documentação do Kubernetes.

resources:
  requests:
    memory: "512Mi"
    cpu: "500m"
  limits:
    memory: "1024Mi"
    cpu: "1000m"

Análises de vivacidade e prontidão

Por padrão, o Kubernetes envia o tráfego para seus pods de app após todos os contêineres no pod serem iniciados e reinicia os contêineres quando eles travam. No entanto, é possível configurar verificações de funcionamento para melhorar a robustez do roteamento de tráfego de serviço.

Por exemplo, seu app pode ter um atraso de inicialização. Os processos de app podem ser iniciados antes que o app inteiro esteja completamente pronto, o que pode afetar as respostas especialmente ao escalar para cima em muitas instâncias. Com as verificações de funcionamento, é possível permitir que seu sistema possa saber se o seu app está em execução e pronto para receber solicitações. Configurando essas análises, é possível também ajudar a evitar o tempo de inatividade ao executar uma atualização contínua de seu app. É possível configurar dois tipos de verificações de funcionamento: análises de vivacidade e de prontidão.

Análise de vivacidade
Configure uma análise de vivacidade para verificar se o contêiner está em execução. Se a análise falha, o contêiner é reiniciado. Se o contêiner não especifica uma análise de vivacidade, a análise é bem-sucedida porque presume que o contêiner está ativo quando o contêiner está em um status Em execução.
Análise de prontidão
Configure uma análise de prontidão para verificar se o contêiner está pronto para receber solicitações e tráfego externo. Se a análise falhar, o endereço IP do pod será removido como um endereço IP utilizável para serviços que correspondem ao pod, mas o contêiner não será reiniciado. Configurar uma sonda de prontidão com um atraso inicial é especialmente importante se o seu aplicativo demorar um pouco para iniciar. Antes do atraso inicial, a análise não é iniciada, fornecendo ao seu contêiner um tempo para ficar ativo. Se o contêiner não fornece uma análise de prontidão, a análise é bem-sucedida porque presume que o contêiner está ativo quando o contêiner está em um status Em execução.

É possível configurar as análises como comandos, solicitações de HTTP ou soquetes TCP. O exemplo usa solicitações de HTTP. Dê ao sensor de vivacidade mais tempo do que à análise de prontidão. Para obter mais informações, consulte a Documentação do Kubernetes.

livenessProbe:
  httpGet:
    path: /
    port: 9080
  initialDelaySeconds: 300
  periodSeconds: 15
readinessProbe:
  httpGet:
    path: /
    port: 9080
  initialDelaySeconds: 45
  periodSeconds: 5

Orçamento de interrupção de pod

Para aumentar a disponibilidade do app, é possível controlar como o app reage a interrupções com base no tipo de disponibilidade desejado com um objeto PodDisruptionBudget.

Um orçamento de interrupção de pod pode ajudá-lo a planejar como o seu app se comporta durante interrupções voluntárias, assim como quando você inicia uma reinicialização direta atualizando a implementação do app, ou interrupções involuntárias, como um pânico kernel. minAvailable : É possível especificar o número ou porcentagem de pods que ainda precisam estar disponíveis após ocorrer uma interrupção.

maxUnavailable
É possível especificar o número ou porcentagem de pods indisponíveis após uma interrupção. O exemplo usa maxUnavailable: 1.
selector
Digite o rótulo para selecionar o conjunto de pods ao qual o PodDisruptionBudget se aplica. Note que se você usou este mesmo rótulo em outras implementações de pod, o pod se aplica a esses também.

Para obter mais informações, consulte a Documentação do Kubernetes.

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: wasliberty
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: wasliberty

Expondo o serviço de app

É possível criar um serviço que exponha seu app. Na seção spec, certifique-se de corresponder os valores de port e de rótulo aos que você usou na implementação. O serviço expõe objetos que correspondem ao rótulo, tal como app: wasliberty no exemplo a seguir.

  • Por padrão, um serviço usa ClusterIP, o que torna o serviço acessível somente dentro do cluster, mas não fora do cluster.
  • É possível criar um serviço NodePort, balanceador de carga ou Ingress para expor o app publicamente. Esses serviços têm dois IPs, um externo e um interno. Quando o tráfego é recebido no IP externo, ele é encaminhado para o IP do cluster interno. Em seguida, por meio do IP do cluster interno, o tráfego é roteado para o IP do contêiner do app.
  • O exemplo usa NodePort para expor o serviço fora do cluster. Para obter mais informações sobre como configurar o acesso externo, consulte Escolhendo um serviço NodePort, Ingress ou de balanceador de carga.
apiVersion: v1
kind: Service
metadata:
  name: wasliberty
  labels:
    app: wasliberty
spec:
  ports:
  - port: 9080
  selector:
    app: wasliberty
    type: NodePort

Se você tiver um requisito para implementar os pods hostNetwork para atender em portas específicas ou para usar um hostPort para expor os pods de app em uma porta específica no nó do trabalhador, use uma porta no intervalo 11000-11200. O IBM Cloud Kubernetes Service designa o intervalo de portas 11000-11200 em nós do trabalhador para esse propósito para evitar conflitos com portas locais e outras usadas pelo IBM Cloud Kubernetes Service. Como os pods hostNetwork e hostPorts referem-se a um endereço IP do nó do trabalhador específico, os pods são limitados para serem executados somente nesse nó do trabalhador. Se houver algum imprevisto, como o nó do trabalhador ser removido ou ficar sem recursos, o pod não poderá ser reprogramado. Para expor uma porta do pod no nó do trabalhador, considere usar um serviçoNodePort no lugar. Para obter mais informações, consulte a Documentação de melhores práticas do Kubernetes.

Configmaps para variáveis de ambiente de contêiner

Os configmaps fornecem informações de configuração não sensíveis para suas cargas de trabalho de implementação.

O exemplo a seguir mostra como é possível referenciar valores de seu configmap como variáveis de ambiente na seção de especificação de contêiner de seu YAML de implementação. Referenciando valores de seu configmap, é possível desacoplar essas informações de configuração de sua implementação para manter seu app conteinerizado móvel.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasliberty
spec:
  replicas: 3
  template:
    ...
    spec:
      ...
      containers:
      - name: wasliberty
        ...
        env:
          - name: VERSION
            valueFrom:
              configMapKeyRef:
                name: wasliberty
                key: VERSION
          - name: LANGUAGE
            valueFrom:
              configMapKeyRef:
                name: wasliberty
                key: LANGUAGE
        ...
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: wasliberty
  labels:
    app: wasliberty
data:
  VERSION: "1.0"
  LANGUAGE: en

Segredos para variáveis de ambiente de contêiner

Os segredos fornecem informações confidenciais de configuração, como senhas para suas cargas de trabalho de implementação.

O exemplo a seguir mostra como é possível referenciar valores de seu segredo como variáveis de ambiente na seção de especificação de contêiner de seu YAML de implementação. Também é possível montar o segredo como um volume. Referenciando valores de seu segredo, é possível desacoplar essas informações de configuração de sua implementação para manter seu app conteinerizado móvel.

Para gerenciamento centralizado de todos os seus segredos entre os clusters e injeção no tempo de execução do aplicativo, tente IBM Cloud Secrets Manager.

apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: wasliberty
  spec:
    replicas: 3
    template:
      ...
      spec:
        ...
        containers:
        - name: wasliberty
          ...
          env:
          - name: username
            valueFrom:
              secretKeyRef:
                name: wasliberty
                key: username
          - name: password
            valueFrom:
              secretKeyRef:
                name: wasliberty
                key: password
          ...
  ---
  apiVersion: v1
  kind: Secret
  metadata:
    name: wasliberty
    labels:
      app: wasliberty
  type: Opaque
  data:
    username: dXNlcm5hbWU=
    password: cGFzc3dvcmQ=

Volumes Persistentes para Armazenamento de Contêiner

Os volumes persistentes (PVs) fazem interface com o armazenamento físico para fornecer armazenamento de dados persistentes para as cargas de trabalho do contêiner.

O exemplo a seguir mostra como é possível incluir armazenamento persistente em seu app. Para provisionar o armazenamento persistente, crie uma solicitação de volume persistente (PVC) para descrever o tipo e o tamanho do armazenamento de arquivo que você deseja ter. Após criar o PVC, o volume persistente e o armazenamento físico são criados automaticamente por meio do provisionamento dinâmico. Referenciando o PVC em seu YAML de implementação, o armazenamento é montado automaticamente em seu pod de app. Quando o contêiner em seu pod grava dados no diretório de caminho de montagem /test, os dados são armazenados na instância de armazenamento de arquivo NFS. Para obter opções sobre outros tipos de armazenamento que é possível provisionar, veja Planejando o armazenamento persistente altamente disponível.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasliberty
spec:
  replicas: 3
  template:
    ...
    spec:
      ...
      containers:
      - name: wasliberty
        ...
        volumeMounts:
        - name: pvmount
          mountPath: /test
      volumes:
      - name: pvmount
        persistentVolumeClaim:
          claimName: wasliberty
        ...
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wasliberty
  annotations:
    volume.beta.kubernetes.io/storage-class: "ibmc-file-bronze"
  labels:
    billingType: "hourly"
    app: wasliberty
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 24Gi

YAML de implementação de exemplo completo

O exemplo a seguir é uma cópia do YAML de implementação que foi discutido seção a seção anteriormente. Também é possível fazer download do YAML por meio do GitHub.

Para aplicar o YAML,

kubectl apply -f file.yaml [-n <namespace>]

Exemplo de YAML

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasliberty
spec:
  replicas: 3
  selector:
    matchLabels:
      app: wasliberty
  template:
    metadata:
      labels:
        app: wasliberty
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - wasliberty
              topologyKey: kubernetes.io/hostname
      containers:
      - name: wasliberty
        image: icr.io/ibm/liberty:latest
        env:
          - name: VERSION
            valueFrom:
              configMapKeyRef:
                name: wasliberty
                key: VERSION
          - name: LANGUAGE
            valueFrom:
              configMapKeyRef:
                name: wasliberty
                key: LANGUAGE
          - name: username
            valueFrom:
              secretKeyRef:
                name: wasliberty
                key: username
          - name: password
            valueFrom:
              secretKeyRef:
                name: wasliberty
                key: password
        ports:
          - containerPort: 9080
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1024Mi"
            cpu: "1000m"
        livenessProbe:
          httpGet:
            path: /
            port: 9080
          initialDelaySeconds: 300
          periodSeconds: 15
        readinessProbe:
          httpGet:
            path: /
            port: 9080
          initialDelaySeconds: 45
          periodSeconds: 5
        volumeMounts:
        - name: pvmount
          mountPath: /test
      volumes:
      - name: pvmount
        persistentVolumeClaim:
          claimName: wasliberty
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: wasliberty
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: wasliberty
---
apiVersion: v1
kind: Service
metadata:
  name: wasliberty
  labels:
    app: wasliberty
spec:
  ports:
  - port: 9080
  selector:
    app: wasliberty
  type: NodePort
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: wasliberty
  labels:
    app: wasliberty
data:
  VERSION: "1.0"
  LANGUAGE: en
---
apiVersion: v1
kind: Secret
metadata:
  name: wasliberty
  labels:
    app: wasliberty
type: Opaque
data:
  username: dXNlcm5hbWU=
  password: cGFzc3dvcmQ=
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wasliberty
  annotations:
    volume.beta.kubernetes.io/storage-class: "ibmc-file-bronze"
  labels:
    billingType: "hourly"
    app: wasliberty
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 24Gi