MCU.TEC Infraestrutura Tutorial Completo de Cloud DevOps com Docker, CI/CD e Monitoramento – Parte 1

Tutorial Completo de Cloud DevOps com Docker, CI/CD e Monitoramento – Parte 1

Introdução ao Tutorial de Cloud DevOps

Este tutorial marca o início da jornada na criação de ambientes reais utilizando Docker dentro do contexto da filosofia DevOps. A proposta vai muito além de ensinar o uso de ferramentas específicas. O objetivo é apresentar uma cultura de colaboração entre desenvolvimento e operações, promovendo automação, escalabilidade e portabilidade em aplicações modernas.

Nesta primeira etapa, o foco está em entender o papel do DevOps como facilitador da entrega contínua, da confiabilidade e da padronização de ambientes. Será demonstrado como uma aplicação desenvolvida em Python pode ser estruturada para ser executada com consistência em diferentes sistemas operacionais.

O conteúdo explora como transformar um projeto funcional em um ambiente local em um serviço encapsulado com Docker, pronto para ser executado de forma segura e escalável, tanto localmente quanto na nuvem.

O tutorial não se aprofunda no código da aplicação em si, mas sim em como tornar a aplicação portátil, replicável e independente do ambiente onde será executada. Essa abordagem reflete o verdadeiro papel do profissional DevOps: garantir que aplicações funcionem de forma previsível e segura em qualquer lugar.

O que é DevOps e sua História

O termo DevOps é uma junção de duas palavras: Development (desenvolvimento) e Operations (operações). Trata-se de uma filosofia de trabalho e conjunto de práticas culturais e técnicas que visam integrar os times de desenvolvimento de software com os times responsáveis pela infraestrutura e operação dos sistemas. O objetivo é automatizar processos, melhorar a colaboração, aumentar a velocidade das entregas e manter a confiabilidade das aplicações em ambientes complexos e em constante mudança.

Origens e Evolução

Na década de 1980, o desenvolvimento de software era centralizado e vinculado diretamente ao hardware. As equipes de desenvolvimento e operação trabalhavam de forma isolada, com pouca comunicação entre si. Essa abordagem seguiu por muito tempo, marcada por ciclos longos e sequenciais de entrega, conhecidos como modelo em cascata (Waterfall).

Com a popularização dos computadores pessoais nos anos 1990, surgiram os primeiros servidores dedicados e aplicações cliente-servidor. Mesmo com esse avanço, as organizações ainda mantinham a separação rígida entre desenvolvimento e operações, o que gerava conflitos, atrasos e uma constante transferência de responsabilidades em caso de falhas.

Foi apenas nos anos 2000, com a introdução do Manifesto Ágil (2001), que começou uma transformação cultural importante. As equipes passaram a trabalhar com ciclos curtos, feedback contínuo e colaboração entre áreas como desenvolvimento, testes e análise de negócios. Contudo, ainda persistia a separação das operações de TI, o que impedia uma integração plena entre todos os envolvidos no ciclo de vida da aplicação.

A virada decisiva ocorreu em 2009, quando foi apresentada publicamente uma abordagem capaz de realizar diversos deploys diários com baixo risco e alta confiabilidade, promovendo a automação de infraestrutura, controle de versão compartilhado e práticas como infraestrutura como código (IaC). Também foi criado, no mesmo ano, o evento internacional DevOpsDays, responsável por consolidar o termo “DevOps” como um movimento mundial. Foi nesse momento que se tornou evidente que mudanças pequenas, contínuas e colaborativas são mais eficazes do que grandes lançamentos esporádicos.

Princípios e Benefícios

DevOps propõe que todos os envolvidos no desenvolvimento de uma aplicação — desde os requisitos até a operação em produção — atuem como um sistema integrado, com confiança mútua, respeito, aprendizado contínuo, e foco em entregar valor com qualidade e estabilidade.

Entre os benefícios da adoção de práticas DevOps, destacam-se:

  • Redução de riscos operacionais e falhas em produção;
  • Maior qualidade nas entregas de software;
  • Aumento da produtividade e eficiência da equipe;
  • Feedback contínuo dos usuários e dos sistemas;
  • Integração contínua (CI) e entrega contínua (CD);
  • Monitoramento proativo e correções rápidas.

DevOps Hoje

Com o avanço de tecnologias como virtualização, containers, cloud computing e orquestração com Kubernetes, DevOps se consolidou como uma abordagem essencial para empresas que desejam escalar suas operações, melhorar a resiliência dos sistemas e reduzir o tempo de resposta às mudanças do mercado.

Essa filosofia é hoje aplicada em diversos setores, incluindo bancos, e-commerce, telecomunicações, saúde e indústria, sendo uma peça-chave na transformação digital e na inovação contínua.

Analisando o Projeto e Criando o Ambiente Virtual com Python

O primeiro passo prático no tutorial é conhecer a aplicação que será utilizada como base para as atividades DevOps. Trata-se de uma aplicação desenvolvida em Python com o framework Flask, escolhida por sua leveza e simplicidade, ideal para demonstrar práticas de empacotamento, deploy e monitoramento.

A estrutura inicial do projeto conta com arquivos básicos como app.py, que contém a lógica principal do servidor, e um diretório de templates com arquivos HTML que definem a interface web da aplicação. Também estão presentes arquivos auxiliares como requirements.txt, onde são listadas todas as dependências necessárias para executar o projeto, e o .gitignore, que define quais arquivos não devem ser versionados.

Para garantir que o ambiente seja consistente em diferentes sistemas operacionais e livre de conflitos com outras bibliotecas instaladas no sistema, é criada uma virtual environment (ambiente virtual Python). Esse ambiente isolado garante que as dependências da aplicação fiquem contidas em um único espaço, controlado e replicável.

O processo de criação do ambiente virtual segue os seguintes passos:

  1. Criação do ambiente virtual: python -m venv venv Isso cria uma pasta chamada venv contendo a estrutura necessária para o isolamento.
  2. Ativação do ambiente:
    • Em sistemas Unix/Linux/macOS: source venv/bin/activate
    • Em sistemas Windows: .\venv\Scripts\activate
    Após ativado, o terminal indicará que o ambiente virtual está ativo, geralmente com o prefixo (venv).
  3. Instalação das dependências da aplicação: pip install -r requirements.txt Isso garante que todas as bibliotecas utilizadas pela aplicação estejam instaladas corretamente no ambiente isolado.

Com o ambiente configurado, o próximo passo será a execução local da aplicação, permitindo verificar se tudo está funcionando corretamente antes de seguir para o empacotamento com Docker.

Executando a Aplicação Localmente e Entendendo o Ciclo de Vida DevOps

Com o ambiente virtual Python ativado e as dependências instaladas, o próximo passo é executar a aplicação localmente. Isso permite garantir que tudo está funcionando corretamente antes de iniciar o processo de empacotamento, automação e deploy.

A aplicação Flask pode ser executada com o seguinte comando:

python app.py

Ao rodar esse comando, o servidor local será iniciado, geralmente escutando na porta 5000. Acesse http://localhost:5000 no navegador para visualizar a interface da aplicação.

Esse processo de execução local é essencial dentro do ciclo de vida DevOps, pois representa a fase de desenvolvimento e validação inicial, onde o código é testado manualmente antes de seguir para etapas mais avançadas como integração contínua, testes automatizados, entrega e monitoramento.

O Ciclo de Vida DevOps

No contexto DevOps, o ciclo de vida de uma aplicação não se limita à sua construção. Ele envolve uma série de etapas que trabalham de forma contínua e colaborativa:

  1. Planejamento
    Levantamento de requisitos, definição de funcionalidades e arquitetura da solução.
  2. Desenvolvimento
    Codificação do sistema com foco em modularidade, testes e versionamento.
  3. Integração e Testes
    Automatização de testes e validações a cada nova modificação no código, utilizando pipelines de integração contínua (CI).
  4. Entrega e Deploy
    Preparação do sistema para produção com automação de build, empacotamento (ex: containers) e publicação.
  5. Operação e Monitoramento
    Garantia da estabilidade da aplicação em produção com uso de ferramentas de observabilidade, logs, alertas e métricas.
  6. Feedback Contínuo
    Retorno constante do sistema, dos usuários e das métricas de produção para ajustes e melhorias contínuas.

Executar a aplicação localmente é uma etapa essencial que precede a automação. Ela garante que todos os requisitos foram corretamente instalados e que a aplicação está apta para ser empacotada — tema da próxima seção do tutorial.

Containerizando a Aplicação com Docker

Agora que a aplicação está funcionando corretamente em ambiente local, o próximo passo é torná-la independente do sistema operacional onde será executada. Isso é feito através do Docker, uma tecnologia que permite empacotar a aplicação e suas dependências em uma imagem leve e portátil chamada container.

O que é Docker?

Docker é uma plataforma que automatiza a criação, o empacotamento e a execução de aplicações em ambientes isolados. Com ele, é possível garantir que a aplicação funcione exatamente da mesma forma em qualquer máquina — seja localmente, em um servidor de produção ou na nuvem.

Diferente de máquinas virtuais, containers compartilham o mesmo kernel do sistema operacional, tornando-os muito mais leves e rápidos.

Criando o Dockerfile

O primeiro passo é criar um arquivo chamado Dockerfile na raiz do projeto. Esse arquivo define instruções passo a passo para construir a imagem da aplicação. Veja um exemplo básico:

# Imagem base oficial do Python
FROM python:3.11

# Diretório de trabalho no container
WORKDIR /app

# Copia os arquivos do projeto para o container
COPY . .

# Instala as dependências da aplicação
RUN pip install -r requirements.txt

# Expõe a porta utilizada pelo Flask
EXPOSE 5000

# Comando para iniciar a aplicação
CMD ["python", "app.py"]

Cada instrução cumpre uma etapa essencial:

  • FROM define a base da imagem (no caso, Python 3.11).
  • WORKDIR cria o diretório interno onde o código será executado.
  • COPY . . transfere os arquivos da aplicação local para dentro do container.
  • RUN executa o comando de instalação das dependências.
  • EXPOSE informa qual porta será usada.
  • CMD define o comando final que roda a aplicação.

Construindo a Imagem Docker

Com o Dockerfile criado, o próximo passo é construir a imagem:

docker build -t app-devops .

Esse comando instrui o Docker a ler o Dockerfile e construir uma imagem chamada app-devops.

Executando a Aplicação no Container

Uma vez criada a imagem, a aplicação pode ser executada dentro de um container com:

docker run -p 5000:5000 app-devops

O parâmetro -p 5000:5000 faz o mapeamento da porta do container para a porta local, permitindo o acesso via http://localhost:5000.

Agora, a aplicação está empacotada com tudo o que precisa para rodar de forma consistente, replicável e portátil. Este é o primeiro passo concreto rumo à automação e à escalabilidade, base para práticas mais avançadas de DevOps.

Versionamento e Imagens Docker Otimizadas

Com a aplicação containerizada e funcionando, é hora de adotar práticas que tornam o processo mais profissional e escalável. Um dos principais aspectos nesse estágio é o versionamento das imagens e a otimização do Dockerfile, garantindo performance, segurança e controle de versões durante o ciclo de vida do projeto.

Por que versionar as imagens?

Ao versionar uma imagem Docker, você evita confusões entre builds diferentes da aplicação. Isso permite, por exemplo:

  • Identificar qual versão do container está em produção;
  • Fazer rollback em caso de falhas;
  • Executar testes A/B entre versões distintas;
  • Facilitar a integração com pipelines de CI/CD.

O versionamento pode ser feito no momento da construção da imagem:

docker build -t app-devops:1.0.0 .

Depois, para executar essa versão específica:

docker run -p 5000:5000 app-devops:1.0.0

Otimizando o Dockerfile

A criação de imagens mais enxutas e seguras impacta diretamente no tempo de build, no espaço ocupado em disco e na segurança do ambiente. Algumas boas práticas incluem:

1. Usar imagens menores (ex: Alpine)

Ao invés de usar python:3.11, que é baseada no Debian e possui muitos pacotes desnecessários, é possível utilizar:

FROM python:3.11-alpine

Essa imagem é significativamente menor, porém requer alguns ajustes nas dependências e ferramentas, pois nem tudo está disponível por padrão.

2. Separar camadas com mais eficiência

Cada comando em um Dockerfile cria uma camada. Agrupar instruções reduz o número de camadas e melhora o cache:

RUN pip install --no-cache-dir -r requirements.txt

3. Ignorar arquivos desnecessários com .dockerignore

Assim como o .gitignore, o .dockerignore evita que arquivos como logs, .venv/, __pycache__/, e outros diretórios desnecessários sejam incluídos na imagem:

__pycache__/
*.pyc
.venv/
.env

Verificando o tamanho e camadas da imagem

Use o comando abaixo para investigar o tamanho da imagem criada:

docker image ls

Ou para examinar as camadas em detalhes:

docker history app-devops:1.0.0

Publicando a imagem no Docker Hub (opcional)

Caso deseje compartilhar sua imagem com outros membros da equipe ou utilizá-la em pipelines CI/CD, é possível enviá-la ao Docker Hub:

docker tag app-devops:1.0.0 seu-usuario/app-devops:1.0.0
docker push seu-usuario/app-devops:1.0.0

Para isso, é necessário ter uma conta gratuita no Docker Hub.

Criando um Pipeline de Integração Contínua (CI)

Após a containerização da aplicação e a adoção de boas práticas com Docker, é hora de automatizar o processo de build e validação utilizando Integração Contínua (Continuous Integration – CI). O objetivo da CI é garantir que, a cada alteração feita no código, a aplicação seja testada, empacotada e validada automaticamente, reduzindo erros e acelerando o ciclo de desenvolvimento.

O que é Integração Contínua?

Integração Contínua é uma prática onde os desenvolvedores enviam mudanças frequentes ao repositório, geralmente hospedado em plataformas como GitHub, GitLab ou Bitbucket. Cada commit dispara uma série de ações automáticas:

  • Download do código atualizado;
  • Execução de testes automatizados;
  • Construção da imagem Docker;
  • Validação do build.

Essas ações são definidas por meio de pipelines, descritas em arquivos YAML que informam à plataforma como automatizar as etapas desejadas.

Exemplo com GitHub Actions

Para projetos hospedados no GitHub, cria-se um arquivo chamado ci.yml dentro do diretório .github/workflows/:

name: CI DevOps Tutorial

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Clonando o repositório
      uses: actions/checkout@v3

    - name: Instalando o Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'

    - name: Instalando dependências
      run: |
        python -m venv venv
        source venv/bin/activate
        pip install -r requirements.txt

    - name: Executando testes
      run: |
        source venv/bin/activate
        python -m unittest discover tests

    - name: Build da imagem Docker
      run: |
        docker build -t app-devops:ci .

Esse pipeline define o seguinte:

  1. O processo é acionado quando há push ou pull request na branch main.
  2. O GitHub Actions baixa o código, instala o Python 3.11 e configura o ambiente virtual.
  3. Instala as dependências e executa os testes contidos no diretório tests/.
  4. Se os testes forem bem-sucedidos, constrói a imagem Docker.

Vantagens do Pipeline de CI

  • Validação automática do código;
  • Redução de erros em produção;
  • Feedback imediato sobre falhas;
  • Base sólida para práticas de CD (entrega contínua).

Este é um passo essencial para garantir que a aplicação esteja sempre pronta para ser implantada, com qualidade e consistência.

Entrega Contínua (CD) e Automação do Deploy

Após configurar a Integração Contínua (CI) para testar e construir a aplicação automaticamente, o próximo passo no fluxo DevOps é implementar a Entrega Contínua (CD – Continuous Delivery). Essa etapa garante que o sistema esteja sempre pronto para ser implantado com segurança em produção, com processos automatizados e previsíveis.

O que é Entrega Contínua?

A Entrega Contínua é uma extensão natural da Integração Contínua. Ela automatiza o processo de deploy em ambientes de teste, homologação ou produção, sempre que um novo build é aprovado. Em um fluxo bem configurado, é possível que o deploy aconteça automaticamente — ou com um simples clique de aprovação.

Como funciona na prática?

Para automatizar o deploy, é comum configurar etapas adicionais no pipeline, como:

  • Publicar a imagem Docker em um registro de containers (ex: Docker Hub, GitHub Container Registry, Amazon ECR);
  • Realizar o deploy em um ambiente controlado (ex: servidor de staging, Kubernetes, máquina virtual ou instância na nuvem);
  • Executar scripts de verificação pós-deploy (ex: health check ou rollback automático).

Continuando o exemplo com GitHub Actions

Vamos expandir o pipeline para que, após o sucesso dos testes e build, a imagem seja enviada para o Docker Hub:

    - name: Login no Docker Hub
      run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin

    - name: Tag e push da imagem
      run: |
        docker tag app-devops:ci ${{ secrets.DOCKER_USERNAME }}/app-devops:latest
        docker push ${{ secrets.DOCKER_USERNAME }}/app-devops:latest

Essas etapas:

  1. Autenticam-se no Docker Hub usando segredos seguros armazenados no GitHub (secrets).
  2. Tagueiam a imagem com latest (ou uma versão, se preferir).
  3. Publicam a imagem no repositório Docker, tornando-a disponível para deploy em qualquer lugar.

Realizando o Deploy

Uma vez publicada, a imagem pode ser implantada com comandos simples, como:

docker pull seu-usuario/app-devops:latest
docker run -d -p 80:5000 seu-usuario/app-devops:latest

Ou, em ambientes mais robustos, o deploy pode ser automatizado via:

  • Docker Compose;
  • Kubernetes com Helm Charts;
  • Plataformas de PaaS como Heroku, Render, ou Google Cloud Run.

Benefícios da Entrega Contínua

  • Deploys mais frequentes e confiáveis;
  • Menor risco de erro humano;
  • Ambientes padronizados e rastreáveis;
  • Maior rapidez na correção de bugs e lançamento de features.

A Entrega Contínua representa um marco na maturidade de equipes DevOps, sendo o elo entre o desenvolvimento ágil e a operação estável e segura.

Monitoramento da Aplicação em Produção

Uma vez que a aplicação está em produção, torna-se essencial acompanhar seu comportamento em tempo real. O monitoramento contínuo permite identificar falhas rapidamente, antecipar problemas de desempenho e garantir que os usuários finais estejam tendo uma boa experiência.

Essa prática é um dos fundamentos do DevOps moderno e deve ser implementada desde os primeiros estágios do projeto, e não apenas quando surgem erros em produção.

O que é monitorar uma aplicação?

Monitorar é mais do que apenas verificar se a aplicação está “no ar”. Envolve:

  • Coleta de métricas como uso de CPU, memória, número de requisições, tempo de resposta e taxa de erro;
  • Análise de logs para identificar falhas, mensagens de erro e comportamentos inesperados;
  • Visualização e alertas, com dashboards e sistemas de notificação automática.

Ferramentas DevOps para Monitoramento

Algumas ferramentas populares que podem ser integradas a projetos Dockerizados e ambientes em nuvem incluem:

  • Prometheus: coleta e armazena métricas em séries temporais. Muito usado com Kubernetes.
  • Grafana: cria dashboards interativos com base nos dados do Prometheus ou outras fontes.
  • Loki: sistema de logs criado pelos mesmos desenvolvedores do Grafana, ideal para uso conjunto.
  • ELK Stack (Elasticsearch + Logstash + Kibana): usado para indexar e visualizar logs em larga escala.

Exemplo simples com Prometheus + Grafana

Se você está rodando a aplicação via Docker, pode usar exporters para coletar métricas. Um exemplo clássico é o Prometheus Python Client.

1. Instalando o pacote no projeto

No requirements.txt, adicione:

prometheus_client

No app.py, adicione:

from prometheus_client import start_http_server, Summary

REQUEST_TIME = Summary('request_processing_seconds', 'Tempo de processamento de requisições')

@app.before_first_request
def start_metrics():
    start_http_server(8000)  # Porta separada para Prometheus coletar métricas

2. Configurando Prometheus

Crie um arquivo prometheus.yml:

global:
  scrape_interval: 10s

scrape_configs:
  - job_name: 'flask-app'
    static_configs:
      - targets: ['localhost:8000']

Inicie o Prometheus com Docker:

docker run -d -p 9090:9090 -v $PWD/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus

Acesse http://localhost:9090 para visualizar as métricas sendo coletadas.

3. Visualizando com Grafana

Com Prometheus rodando, instale o Grafana:

docker run -d -p 3000:3000 grafana/grafana

Acesse http://localhost:3000, conecte ao Prometheus como fonte de dados e crie dashboards para acompanhar o tempo de resposta, volume de requisições, e muito mais.

Por que monitorar desde o início?

  • Reduz o tempo de detecção de falhas (MTTD);
  • Facilita decisões baseadas em dados reais;
  • Melhora a experiência do usuário final;
  • Ajuda a provar o valor do DevOps com indicadores claros.

Feedback Contínuo e Melhoria Constante

A última etapa — e talvez a mais crítica — do ciclo DevOps é o feedback contínuo. Sem ele, todo o esforço em automação, integração, entrega e monitoramento perde seu valor estratégico. O feedback contínuo conecta as experiências reais de uso com o time de desenvolvimento e operação, possibilitando aprendizado rápido, respostas ágeis e melhorias sustentáveis ao longo do tempo.

O que é feedback contínuo?

No contexto DevOps, feedback contínuo é o processo de coletar informações em tempo real sobre o comportamento do sistema em produção e compartilhar essas informações com todos os envolvidos:

  • Desenvolvedores;
  • Engenheiros de operações;
  • Analistas de negócios;
  • Responsáveis por produto;
  • E até usuários finais (indiretamente, por meio de métricas de uso).

Fontes de feedback no ciclo DevOps

O feedback pode vir de diversas fontes:

  • Logs e métricas (via Prometheus, Grafana, Loki, etc.);
  • Alertas de monitoramento (ex: erros 500, lentidão, uso excessivo de CPU);
  • Feedback dos usuários via canais de suporte, UX, redes sociais;
  • Relatórios de CI/CD, como testes quebrando após merge;
  • Acompanhamento de deploys e rollback.

Como utilizar o feedback de forma eficaz

  1. Automatize a coleta e exibição: dashboards claros, alertas inteligentes e integração com canais como Slack ou e-mail ajudam a manter o time informado.
  2. Integre o feedback à cultura da equipe: reuniões rápidas (como daily meetings) podem incluir uma visão rápida dos indicadores e eventos recentes.
  3. Aja rapidamente sobre os dados recebidos: não basta coletar — o mais importante é agir com base no que se aprende. Se uma feature nova aumentou o tempo de resposta, revise. Se um bug surgiu após um deploy, resolva e documente.
  4. Itere com base em dados, não suposições: DevOps é sobre melhoria contínua. Métricas reais substituem achismos e ajudam a tomar decisões mais sólidas.

Ferramentas que auxiliam o feedback contínuo

  • Grafana: dashboards de métricas e alertas.
  • Elastic/Kibana ou Loki: visualização e análise de logs.
  • Sentry: rastreamento de exceções e falhas front-end/back-end.
  • Google Analytics / Matomo: comportamento de usuários.
  • GitHub Issues / Jira: rastreamento de feedback de usuários e bugs.

Conclusão do Tutorial

Com este ciclo completo — planejamento, desenvolvimento, integração, entrega, operação, monitoramento e feedback — você experimentou a essência do DevOps. Mais do que uma lista de ferramentas ou práticas, DevOps é uma cultura de colaboração e melhoria contínua, que ajuda organizações a entregarem mais valor com mais velocidade e menos riscos.

Ao longo do tutorial, você:

  • Criou uma aplicação Python e preparou seu ambiente;
  • Empacotou a aplicação com Docker;
  • Automatizou os testes e o build com CI;
  • Realizou o deploy com práticas de CD;
  • Monitorou a aplicação em produção;
  • E finalmente, fechou o ciclo com feedback contínuo.

Este é apenas o começo. A partir daqui, você pode explorar temas como:

  • Orquestração com Kubernetes;
  • Pipelines avançadas de CI/CD com GitLab ou Jenkins;
  • Segurança DevSecOps;
  • Observabilidade com OpenTelemetry;
  • e muito mais.

Repositórios da Alura para este Tutorial:

  • https://github.com/guilhermeonrails/ellis
  • https://github.com/guilhermeonrails/refactored-octo-chainsaw
0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

0
Adoraria saber sua opinião, comente.x