Variável de ambiente - (ReactJS, Docker, AKS)
Tags: react, docker, kubernetes
Published: 2021-01-28 22:48:23

A maioria dos projetos hoje em dia são desenvolvidos para diversos ambientes de trabalho (homolog, staging, production). E junto com esses ambientes temos a preocupação com a infraestrutura que o projeto vai utilizar, além do **Framework** utilizado no front-end para usabilidade e visualização de conteúdo.

Seguindo nesse raciocínio vamos falar sobre como utilizar variável de ambiente em um projeto que utiliza **ReactJS, Docker e AKS**, fazendo uma configuração simples para conseguir consumir a URL de uma API diferente de acordo com o ambiente em que o usuário está.

Após criar o projeto base com o **`npx create-react-app my-app`** vamos criar uma pasta onde ficará as configurações de variáveis para cada ambiente.

**`environments/base.js`**
```sh
export default {
  api: 'http://localhost:5000/api'
}
```
O **`base.js`** é um arquivo inicial com o proposito de ser utilizado se nenhum outro arquivo ambiente for detectado pelas configurações.

**`environments/Homolog.js`**
```sh
export default {
  api: 'http://suaapi.hlg.seudominio.com.br/api',
};
```

**`environments/Staging.js`**
```sh
export default {
  api: 'http://suaapi.stg.seudominio.com.br/api',
};
```

**`environments/Production.js`**
```sh
export default {
  api: 'http://suaapi.prd.seudominio.com.br/api',
};
```

Agora para finalizar as configurações das variáveis de ambiente vamos criar um último arquivo.

**`environments/index.js`**
```sh
import base from './base';

if (window.ENV) {
  localStorage.setItem('@environment', window.ENV);

  delete window.ENV;
}

const envClient = localStorage.getItem('@environment');
const environments = envClient || 'base';

const env = require('./' + environments).default;

export default {
  ...base,
  ...env,
}
```
Nessa configuração verificamos se existe algum valor em **`window.ENV`** se existir valor salvamos ele no **`localStorage`** e deletamos o **`window.ENV`**, depois recuperamos esse valor salvo no **`localStorage`** para utiliza-lo e caso esse valor não exista utilizamos **`'base'`**.
Ao final da configuração criamos uma nova importação utilizando **`require`** onde ela importa o arquivo correto utilizando a variável **`environments`** que pode conter o valor do **`localStorage`** ou **`'base'`** e na conclusão exportamos as variáveis **`base`** e **`env`** onde teremos as informações necessárias para utilizar mais tarde.

Seguindo na configuração de variáveis de ambiente eu fiz uma alteração no **`App.js`**, essa alteração é simplesmente para exibir a variável **`env.api`** que vai ser consumida das configurações que fizemos anteriormente na pasta environments.

**`App.js`**
```sh
import React from 'react';

import logo from './logo.svg';
import env from './environments';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>{env.api}</p>
      </header>
    </div>
  );
}

export default App;
```

E para finalizar a configuração inicial e antes de partirmos para a criação da imagem do Docker e as configurações .YAML do Kubernetes, faremos algumas modificações na pasta **`public`**.

Primeiro vamos criar um novo arquivo .js dentro dela.

**`config.js`**
```sh
window.ENV = "Homolog"
```

Dentro desse arquivo temos uma configuração do **`window.ENV`** que utilizamos para leitura dentro do **`environments/index.js`** e que futuramente vai ser alterada via Kubernetes.

Logo após a criação desse arquivo vamos adicionar mais um script dentro do arquivo **`index.html`** que fica na pasta public.

**`index.html`**
```sh
<script type="text/javascript" src="%PUBLIC_URL%/config.js"></script>
```

Essa alteração é para o projeto conseguir consumir o arquivo **`config.js`** após o build do projeto.


<h2>Configuração de build e Kubernetes</h2>

Antes de criar a imagem do projeto no Docker vamos primeiro executar o build do próprio ReactJS, o que vai facilitar a criação da imagem no Docker.

```sh
npm run build / yarn build
```

Para criação da imagem do Docker o arquivo **`Dockerfile`** ficará da seguinte maneira:

**`Dockerfile`**
```sh
# Stage 1
FROM nginx:1.17

# Stage 2
COPY build/ /usr/share/nginx/html
```

Além de já utilizar o build que foi feito pelo ReactJS anteriormente, utilizamos o nginx para rodar o html do projeto. Vale lembrar que o arquivo **`.dockerignore`** não deve conter a pasta build, porque se não ocorrerá um erro quando a imagem for criada.

Seguiremos para criação da imagem do Docker executando os comando na sequencia:

```sh
1 - Criando a imagem do projeto
docker build -t react-docker-aks .

2 - Criação do registro no Docker local
docker run -d -p 5000:5000 --restart=always --name registry registry:2

3 - Criação de tag para referencia da imagem
docker tag react-docker-aks localhost:5000/react-docker-aks

4 - Fazendo o push da imagem para o Docker local
docker push localhost:5000/react-docker-aks
```

Obs.: é importante lembrar que para executar os comandos acima é necessário ter o Docker Desktop instalado na máquina.

E para finalizar criaremos os arquivos .YAML que são utilizados pelo Kubernetes para configuração dos clusters.

**``deployment.yaml``**
```sh
apiVersion: v1
kind: ConfigMap
metadata:
  name: react-docker-aks
  labels:
    app: react-docker-aks
data:
  config.js: "window.ENV = \"Homolog\";"
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: react-docker-aks
spec:
  replicas: 1
  selector:
    matchLabels:
      app: react-docker-aks
  template:
    metadata:
      labels:
        app: react-docker-aks
    spec:
      containers:
        - name: react-docker-aks
          image: localhost:5000/react-docker-aks
          imagePullPolicy: Always
          ports:
            - containerPort: 80
          volumeMounts:
            - name: config-js
              mountPath: /usr/share/nginx/html/config.js
              subPath: config.js
      volumes:
        - name: config-js
          configMap:
            name: react-docker-aks
      restartPolicy: Always
---
kind: Service
apiVersion: v1
metadata:
  name: react-docker-aks
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
      nodePort: 31000
  selector:
    app: react-docker-aks
```

Na criação dos arquivos .YAML utilizamos uma estrutura padrão de configuração adicionando apenas alguns métodos que serviram para leitura e configuração do arquivo **``config.js``** que está na pasta public do projeto. São eles:

No **``ConfigMap``** adicionei
```sh
data:
  config.js: "window.ENV = \"Homolog\";"
```

No **``Deployment``** dentro de spec containers
```sh
volumeMounts:
  - name: config-js
    mountPath: /usr/share/nginx/html/config.js
    subPath: config.js
```

E a criação do volumes dentro do spec
```sh
volumes:
  - name: config-js
    configMap:
      name: react-docker-aks
```

Foi uma escolha minha criar todas as configurações dentro do arquivo **``deployment.yaml``** mas da certo criando os arquivos separados em **``configmap.yaml``**, **``deployment.yaml``** e **``service.yaml``**, e depois criando o **``kustomization.yaml``** para juntar todos eles.

Para simular outros ambientes eu criei mais dois arquivos **``deployment.yaml``** o **``deployment-staging.yaml``** e **``deployment-production.yaml``** que tem o mesmo conteúdo do primeiro só modificando namespace, o valor do window.ENV e criando uma outra porta pro service.

**``deployment-staging.yaml``**
```sh
apiVersion: v1
kind: ConfigMap
metadata:
  name: react-docker-aks-stg
  labels:
    app: react-docker-aks-stg
data:
  config.js: "window.ENV = \"Staging\";"
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: react-docker-aks-stg
spec:
  replicas: 1
  selector:
    matchLabels:
      app: react-docker-aks-stg
  template:
    metadata:
      labels:
        app: react-docker-aks-stg
    spec:
      containers:
        - name: react-docker-aks-stg
          image: localhost:5000/react-docker-aks
          imagePullPolicy: Always
          ports:
            - containerPort: 80
          volumeMounts:
            - name: config-js
              mountPath: /usr/share/nginx/html/config.js
              subPath: config.js
      volumes:
        - name: config-js
          configMap:
            name: react-docker-aks-stg
      restartPolicy: Always
---
kind: Service
apiVersion: v1
metadata:
  name: react-docker-aks-stg
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
      nodePort: 31001
  selector:
    app: react-docker-aks-stg
```

**``deployment-production.yaml``**
```sh
apiVersion: v1
kind: ConfigMap
metadata:
  name: react-docker-aks-prd
  labels:
    app: react-docker-aks-prd
data:
  config.js: "window.ENV = \"Production\";"
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: react-docker-aks-prd
spec:
  replicas: 1
  selector:
    matchLabels:
      app: react-docker-aks-prd
  template:
    metadata:
      labels:
        app: react-docker-aks-prd
    spec:
      containers:
        - name: react-docker-aks-prd
          image: localhost:5000/react-docker-aks
          imagePullPolicy: Always
          ports:
            - containerPort: 80
          volumeMounts:
            - name: config-js
              mountPath: /usr/share/nginx/html/config.js
              subPath: config.js
      volumes:
        - name: config-js
          configMap:
            name: react-docker-aks-prd
      restartPolicy: Always
---
kind: Service
apiVersion: v1
metadata:
  name: react-docker-aks-prd
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
      nodePort: 31002
  selector:
    app: react-docker-aks-prd
```

Para executar o Kubernetes no Docker Desktop é necessário ativar-lo e também é preciso ter o Minikube instalado para executar os comandos que virão na sequencia.

**``Ativando o Kubernetes``**
<img alt="Ativando o Kubernetes no Docker-Desktop" src="https://github.com/felipedesenna/react-docker-aks/raw/main/src/img/kubernetes-docker-desktop.png" width="830px" />

**``Executando os comandos``**
```sh
1 - Criando o contexto padrão para o cluster do Kubernetes
kubectl config use-context docker-desktop

2 - Criando o serviço Kubernetes
kubectl apply -f deployment.yaml

3 - Serviço do ambiente staging
kubectl apply -f deployment-staging.yaml

4 - Serviço do ambiente production
kubectl apply -f deployment-production.yaml
```

Os ambientes ficaram disponivéis nas URLs:
**HLG**
http://localhost:31000/

**STG**
http://localhost:31001/

**PRD**
http://localhost:31002/

E assim se concluí o projeto e todas as configurações realizadas para conseguir ler uma variável de ambiente após o build do ReactJS via Kubernetes.
O projeto todo pode ser acessado e baixado no meu github: https://github.com/felipedesenna/react-docker-aks