O objetivo deste desafio é criar uma função do Azure (Azure Function) para receber, através de uma requisição HTTP POST, informações de log de uma aplicação que deverão ser armazenadas em um banco de dados que posteriormente foi utilizado para criação de um dashboard na implementação de monitoramento via stack ELK. Na segunda parte do desafio, criamos uma função de TimeTrigger e utilizamos o Kafka para ler um topico da aplicação do desafio 3, esse topico tem informações sobre alteração do cadastro do cliente. Após a leitura desse topico armazenaremos essas informações com Redis em cache para manter atualizada a informação de alteração de cadastro do cliente.
- C#
- Azure Functions
- Elastic Search
- Kibana
- Kafka
- Serilog
- SonarQube
- Horusec
- Redis
- xUnit
- Configurar CI no GitActions
- Configurar Azure
- Configurar Banco de Dados
- Configurar SonarQube
- Configurar Horusec
No portal do Git Hub, na aba Actions
crie um New Workflow
e escolha a opção set it up your self
e crie uma pasta workflow para armazenar os arquivos .yml Nome-Do-Projeto/.github/workflows/deploy.yml
e posteriormente os arquivos de analise do Horusec e Sonarqube de cada projeto, Nome-Do-Projeto/.github/workflows/code-analizer-logs.yml
.
Para a pipeline funcionar, é necessário adicionar algumas variaveis de ambiente.
Em Settings
na aba Secrets
adicione as variaveis.
AZURE_FUNCTIONAPP_PUBLISH_PROFILE
- O conteúdo dessa variável é encontrado dentro do portal do Azure dentro do Aplicativo de Função
que você criou para esse projeto, clicando na opção você vai fazer download de um arquivo .PublishSettings
, abre em um editor e copie seu conteúdo para criar a variavel de ambiente.
AZURE_FUNCTIONAPP_NAME: logs-vaivoa-turma1
- Nome da função criada no Portal do Azure
AZURE_FUNCTIONAPP_PACKAGE_PATH: src/LogsVaivoa
- Caminho da função no diretorio do projeto
on:
push:
branches: [ main ]
env:
AZURE_FUNCTIONAPP_NAME: logs-vaivoa-turma1 # set this to your application's name
AZURE_FUNCTIONAPP_PACKAGE_PATH: src/LogsVaivoa # set this to the path to your web app project, defaults to the repository root
DOTNET_VERSION: '3.x' # set this to the dotnet version to use
#ROOT_SOLUTION_PATH: src
jobs:
build-and-deploy:
runs-on: windows-latest
environment: dev
steps:
- name: 'Checkout GitHub Action'
uses: actions/checkout@master
- name: Setup DotNet ${{ env.DOTNET_VERSION }} Environment
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: 'Resolve Project Dependencies Using Dotnet'
shell: pwsh
run: |
pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}'
dotnet build --configuration Release --output ./output
popd
- name: 'Run Azure Functions Action'
uses: Azure/functions-action@v1
id: fa
with:
app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }}
package: '${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/output'
publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }}
name: code-analizer-logs
on:
push:
branches: [ main, develop ]
workflow_dispatch:
env:
AZURE_FUNCTIONAPP_NAME: logs-vaivoa-turma1
AZURE_FUNCTIONAPP_PACKAGE_PATH: src/LogsVaivoa
DOTNET_VERSION: '3.x'
jobs:
horusec-security:
name: horusec-security
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Running Horusec Security
run: |
curl -fsSL https://raw.githubusercontent.com/ZupIT/horusec/main/deployments/scripts/install.sh | bash -s latest
horusec start -p="./src/" -e="true"
sonarqube-analizer:
needs: [horusec-security]
runs-on: windows-latest
environment: dev
steps:
- name: 'Checkout GitHub Action'
uses: actions/checkout@master
- name: Setup DotNet ${{ env.DOTNET_VERSION }} Environment
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Setup DotNet 5.0 Environment
uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.x'
- name: Setup sonarqube
shell: pwsh
run: |
dotnet tool install --global dotnet-sonarscanner
dotnet tool install --global coverlet.console
- name: Run Sonarqube scanner
shell: pwsh
run: |
cd "./src/LogsVaivoa"
dotnet sonarscanner begin /k:"Logs" /d:sonar.cs.opencover.reportsPaths="opencover.xml" /d:sonar.host.url=${{secrets.SONAR_HOST}} /d:sonar.qualitygate.wait=true /d:sonar.login=${{secrets.SONAR_TOKEN}}
dotnet build
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput=coverage /p:Exclude=[xunit.*]* ./LogsVaivoa.sln
move ..\..\test\Logs.Test\coverage.opencover.xml opencover.xml -force
dotnet sonarscanner end /d:sonar.login=${{secrets.SONAR_TOKEN}}
Variaveis de Ambiente
- SONAR_HOST - Essa variável é a URL.
- SONAR_TOKEN - Quando você criar o projeto no SonarQube, será gerado um token, esse token é essa variavel de ambiente.
- Criar uma conta no portal da
Azure
- Criar um
Grupo de Recursos
Apartir de agora, todas as funções criadas dentro do azure deve ser incluido dentro deste grupo de recurso que neste projeto nomeamosDesafio4-Turma1-FM
- Criar um
Aplicativo Função
no portal doAzure
. Nesse projeto chamamos delogs-vaivoa-turma1
.
- Criar um
Banco de Dados SQL
- Nesse projeto nomeamos o banco de dados comoDbLog
- Para criar uma tabela nesse banco de dados é necessário acessar o Visual Studio, e quando a função já estiver criada, vá em
SQL SERVER EXPLORER
, clique com o botão direito emSQL SERVER
, clique emADD SERVER
, vá na opçãoAzure
, se conecte a sua conta e escolha o servidor que foi criadoDbLog
. - Acesse o
DbLog
, e clique com botão direito emTables
e selecione a opçãoAdd new Table
. - Nomeamos a tabelaLogs
Para configurar o Horusec
na nossa pipeline, é necessário apenas colar o código abaixo no jobs
do arquivo .yml.
jobs:
horusec-security:
name: horusec-security
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Running Horusec Security
run: |
curl -fsSL https://raw.githubusercontent.com/ZupIT/horusec/main/deployments/scripts/install.sh | bash -s latest
horusec start -p="./" -e="true"
Para configurar o SonarQube
na nossa pipeline, é necessário subir o SonarQube em uma marquina virtual. E para esse projeto decidimos simplificar, e usamos a marquina virtual AWS EC2
e usamos um orquestrador de containers, o Caphover
. Dessa forma usamos o SonarQube
dentro de um container e não precisamos subir ele e mais banco de dados na VM, economizando custos para esse projeto.
Para instalar o CapHover
é só abrir o console do AWS e colar o código abaixo.
sudo bash
sudo amazon-linux-extras install docker
sudo service docker start
sudo systemctl enable docker
sudo usermod -a -G docker ec2-user
sudo docker run -p 80:80 -p 443:443 -p 3000:3000 -v /var/run/docker.sock:/var/run/docker.sock -v /captain:/captain caprover/caprover
Ao abrir o CapHover
você vai procurar pelo SonarQube e seguir o passo a passo para subir o container e configura-lo.
Para esse projeto usamos o Elastic Cloud.
- Crie uma conta no ElasticCloud
- Pegar a ElasticSearch endpoint url - No projeto chamamos de ElkConnection
- Criar uma Interface e um Serviço para conexão com Elastic Service
No site do Elastic Cloud, crie um deployment, salve as credenciais.
Todos os endpoints que você venha a precisar estaram no dashboard inicial do seu Deployment. No projeto usamos o endpoint do Elasticsearch
É possivel também trabalhar com elastic search local, usando o docker.
public interface IElasticsearchService
{
public Task<bool> SendToElastic<T>(T log, string index) where T : class;
}
public class ElasticsearchService : IElasticsearchService
{
private ElasticClient _elasticClient;
public ElasticsearchService()
{
var uriElastic = new Uri(Environment.GetEnvironmentVariable("ElkConnection")!);
var elasticsearchSettings = new ConnectionSettings(uriElastic);
_elasticClient = new ElasticClient(elasticsearchSettings);
}
public async Task<bool> SendToElastic<T>(T log, string index) where T : class
{
var resultElastic = await _elasticClient
.IndexAsync(log, idx => idx.Index(index));
return resultElastic.IsValid;
}
}
public class LogService : ILogService
{
private static readonly string IndexLog = Environment.GetEnvironmentVariable("IndexLog");
private readonly IElasticsearchService _elasticService;
private readonly ILogger<LogService> _logger;
private readonly ILogRepository _logRepository;
public LogService(IElasticsearchService elasticService, ILogger<LogService> logger, IDbContext dbContext, ILogRepository logRepository)
{
_elasticService = elasticService;
_logger = logger;
_logRepository = logRepository;
}
public async Task<(bool, object)> PostLog(Log log)
{
if (!log.IsValid()) return (false, log.GetErrors());
await _elasticService.SendToElastic(log, IndexLog);
await _logRepository.InsertLogDb(log);
return (true, log);
}
}