Pesquisa de site

Como usar funções e ambientes no Chef para controlar as configurações do servidor


Introdução

À medida que você constrói sua infraestrutura, o gerenciamento de muitos servidores, serviços, usuários e aplicativos pode se tornar difícil rapidamente. Os sistemas de gerenciamento de configuração podem ser usados para ajudá-lo a gerenciar essa confusão.

O Chef é um excelente sistema de gerenciamento de configuração que pode permitir que você configure diferentes componentes de seu sistema geral com muita facilidade. Nos guias anteriores, discutimos como criar livros de receitas simples para gerenciar a configuração.

Neste guia, continuaremos a explorar como você pode gerenciar seu ambiente com o Chef. Desta vez, falaremos sobre como usar funções e ambientes para diferenciar seus servidores e serviços com base no tipo de funcionalidade que eles devem exibir.

Assumiremos que você instalou seu servidor, estação de trabalho e cliente e que possui os livros de receitas que criamos em nosso último guia disponíveis.

Funções e ambientes

O que é um papel?

Em sua organização, se sua infraestrutura crescer para atender às demandas de tráfego mais alto, provavelmente haverá vários servidores redundantes que executam as mesmas tarefas básicas. Por exemplo, podem ser servidores da Web para os quais um balanceador de carga transmite solicitações. Todos eles teriam a mesma configuração básica e pode-se dizer que cada um cumpre o mesmo papel.

A visão do chef sobre as funções é quase inteiramente a mesma que a definição regular. Uma função no Chef é uma categorização que descreve o que uma máquina específica deve fazer. Quais responsabilidades ele tem e quais softwares e configurações devem ser atribuídos a ele.

Em diferentes situações, você pode ter certas máquinas lidando com mais de uma função. Por exemplo, se você estiver testando seu software, um servidor pode incluir o banco de dados e os componentes do servidor web, enquanto na produção, você planeja tê-los em servidores separados.

Com o Chef, isso pode ser tão fácil quanto atribuir o primeiro servidor a ambas as funções e, em seguida, atribuir cada função a computadores separados para suas máquinas de produção. Cada função conterá os detalhes de configuração necessários para colocar a máquina em um estado totalmente operacional para cumprir sua função específica. Isso significa que você pode reunir livros de receitas que lidarão com instalações de pacotes, configuração de serviços, atributos especiais para essa função, etc.

O que é um Ambiente?

Relacionado à ideia de função está o conceito de ambientes Chef. Um ambiente é simplesmente uma designação destinada a ajudar um administrador a saber em que estágio do processo de produção um servidor faz parte. Cada servidor pode fazer parte de exatamente um ambiente.

Por padrão, um ambiente chamado \_default é criado. Cada nó será colocado neste ambiente, a menos que outro ambiente seja especificado. Os ambientes podem ser criados para marcar um servidor como parte de um grupo de processos.

Por exemplo, um ambiente pode ser chamado de teste e outro pode ser chamado de produção. Como você não quer nenhum código ainda em teste em suas máquinas de produção, cada máquina só pode estar em um ambiente. Você pode então ter uma configuração para máquinas em seu ambiente de teste e uma configuração completamente diferente para computadores em produção.

No exemplo acima fornecido em funções, você pode especificar que em seu ambiente de teste, as funções de servidor da Web e de banco de dados estarão em uma única máquina. Em seu ambiente de produção, essas funções devem ser executadas por servidores individuais.

Os ambientes também ajudam no próprio processo de teste. Você pode especificar que, em produção, um livro de receitas deve ser uma versão estável. No entanto, você pode especificar que, se uma máquina fizer parte do ambiente de teste, ela poderá receber uma versão mais recente do livro de receitas.

Como usar funções

Crie uma função usando o Ruby DSL

Podemos criar funções usando o diretório roles em nosso diretório chef-repo em nossa estação de trabalho.

Faça login em sua estação de trabalho e vá para este diretório agora:

cd ~/chef-repo/roles

Dentro deste diretório, podemos criar diferentes arquivos que definem as funções que queremos em nossa organização. Cada arquivo de função pode ser escrito em Ruby DSL do Chef ou em JSON.

Vamos criar uma função para nosso servidor web:

nano web_server.rb

Dentro deste arquivo, podemos começar especificando alguns dados básicos sobre o papel:

name "web_server"
description "A role to configure our front-line web servers"

Estes devem ser bastante diretos. O nome que damos não pode conter espaços e geralmente deve corresponder ao nome do arquivo que selecionamos para esta função, menos a extensão. A descrição é apenas uma mensagem legível por humanos sobre o que a função deve gerenciar.

Em seguida, podemos especificar a run_list que desejamos usar para essa função específica. A run_list de uma função pode conter livros de receitas (que executarão a receita padrão), receitas de livros de receitas (conforme especificado usando a sintaxe cookbook::recipe) e outras funções. Lembre-se, uma run_list é sempre executada sequencialmente, então coloque os itens de dependência antes dos outros itens.

Se quiséssemos especificar que run_list deveria ser exatamente o que configuramos no último guia, teríamos algo parecido com isto:

name "web_server"
description "A role to configure our front-line web servers"
run_list "recipe[apt]", "recipe[nginx]"

Também podemos usar run_lists específicas do ambiente para especificar alterações de configuração de variáveis, dependendo de qual ambiente um servidor pertence.

Por exemplo, se um nó estiver no ambiente de \produção, você pode querer executar uma receita especial em seu livro de receitas \nginx para adequar esse servidor aos requisitos da política de produção. Você também pode ter uma receita no livro de receitas nginx destinada a configurar alterações especiais para testar servidores.

Assumindo que essas duas receitas são chamadas de \config_prod e \config_test respectivamente, poderíamos criar algumas listas de execução específicas do ambiente como esta:

name "web_server"
description "A role to configure our front-line web servers"
run_list "recipe[apt]", "recipe[nginx]"
env_run_lists "production" => ["recipe[nginx::config_prod]"], "testing" => ["recipe[nginx::config_test]"]

No exemplo acima, especificamos que, se o nó fizer parte do ambiente de produção, ele deverá executar a receita \config_prod no livro de receitas \nginx. No entanto, se o nó estiver no ambiente de teste, ele executará a receita \config_test. Se um nó estiver em um ambiente diferente, a run_list padrão será aplicada.

Da mesma forma, podemos especificar atributos padrão e de substituição. Você deve estar familiarizado com os atributos padrão neste momento. Em nossa função, podemos definir atributos padrão que podem substituir qualquer um dos atributos padrão definidos em qualquer outro lugar.

Também podemos definir atributos de substituição, que têm precedência maior do que muitas outras declarações de atributo. Podemos usar isso para tentar forçar os nós atribuídos a essa função a se comportarem de uma determinada maneira.

Em nosso arquivo, eles podem ser adicionados assim:

name "web_server"
description "A role to configure our front-line web servers"
run_list "recipe[apt]", "recipe[nginx]"
env_run_lists "production" => ["recipe[nginx::config_prod]"], "testing" => ["recipe[nginx::config_test]"]
default_attributes "nginx" => { "log_location" => "/var/log/nginx.log" }
override_attributes "nginx" => { "gzip" => "on" }

Aqui definimos um local de log padrão para todos os servidores no nó. Também especificamos que, apesar do que algumas outras declarações de atributo declararam em outros locais, os nós nesta função devem ter o atributo gzip definido como \on. Isso pode ser substituído em mais alguns locais, mas geralmente é uma precedência alta declaração.

Criar uma função usando JSON

O outro formato que você pode usar para configurar funções é o JSON. Na verdade, podemos explorar esse formato usando faca para criar automaticamente uma função nesse formato. Vamos criar uma função de teste:

knife role create test

Seu editor de texto será aberto com um arquivo de modelo de função pré-carregado. Deve ser algo como isto:

{
  "name": "test",
  "description": "",
  "json_class": "Chef::Role",
  "default_attributes": {
  },
  "override_attributes": {
  },
  "chef_type": "role",
  "run_list": [

  ],
  "env_run_lists": {
  }
}

Esta é basicamente a mesma informação que inserimos no arquivo Ruby DSL formatado. As únicas diferenças são a formatação e a adição de duas novas chaves chamadas json_class e chef_type. Estes são usados internamente e não devem ser modificados.

Fora isso, podemos facilmente recriar nosso outro arquivo em JSON com algo como:

{
  "name": "web_server",
  "description": "A role to configure our front-line web servers",
  "json_class": "Chef::Role",
  "default_attributes": {
    "nginx": {
      "log_location": "/var/log/nginx.log"
    }
  },
  "override_attributes": {
    "nginx": {
      "gzip": "on"
    }
  },
  "chef_type": "role",
  "run_list": [
    "recipe[apt]",
    "recipe[nginx]"
  ],
  "env_run_lists": {
    "production": [
      "recipe[nginx::config_prod]"
    ],
    "testing": [
      "recipe[nginx::config_test]"
    ]
  }
}

Isso deve ter praticamente a mesma funcionalidade da versão Ruby acima.

Transferindo funções entre a estação de trabalho e o servidor

Quando salvamos um arquivo JSON criado usando o comando faca, a função é criada no servidor Chef. Por outro lado, nosso arquivo Ruby que criamos localmente não é carregado no servidor.

Podemos enviar o arquivo ruby para o servidor executando um comando parecido com este:

Isso carregará nossas informações de função especificadas em nosso arquivo para o servidor. Isso funcionaria com o arquivo formatado Ruby DSL ou um arquivo JSON.

Da mesma forma, se quisermos obter nosso arquivo JSON do servidor, podemos dizer ao comando knife para mostrar esse arquivo de função em JSON e, em seguida, canalizá-lo para um arquivo como este:

Atribuindo funções aos nós

Agora, independentemente do formato que usamos, temos nossa função no servidor Chef. Como atribuímos a um nó uma determinada função?

Atribuímos uma função a um nó da mesma forma que faríamos com uma receita, na run_list do nó.

Portanto, para adicionar nossa função a um nó, encontraríamos o nó emitindo:

knife node list

E então daríamos um comando como:

Isso trará o arquivo de definição do nó, o que nos permitirá adicionar uma função à sua run_list:

{
  "name": "client1",
  "chef_environment": "_default",
  "normal": {
    "tags": [

    ]
  },
  "run_list": [
    "recipe[nginx]"
  ]
}

Por exemplo, podemos substituir nossa receita por nossa função neste arquivo:

]

},

Isso executará as mesmas etapas de nossas receitas anteriores, mas simplesmente fala sobre a função que o servidor deve ter.

Isso permite que você acesse todos os servidores em uma função específica por pesquisa. Por exemplo, você pode pesquisar todos os servidores de banco de dados em seu ambiente de produção pesquisando uma função e um ambiente:

knife search "role:database_server AND chef_environment:prod" -a name

Isso fornecerá uma lista dos nós que estão configurados como um servidor de banco de dados. Você pode usar isso internamente em seus livros de receitas para configurar um servidor web para adicionar automaticamente todos os servidores de banco de dados de produção em seu pool para fazer solicitações de leitura.

Como usar ambientes

Criando um ambiente

De certa forma, os ambientes são bastante semelhantes às funções. Eles também são usados para diferenciar servidores diferentes, mas em vez de diferenciar pela função do servidor, os ambientes diferenciam pela fase de desenvolvimento a que uma máquina pertence.

Discutimos um pouco disso antes, quando falamos sobre funções. Os ambientes que coincidem com o ciclo de vida real do produto fazem mais sentido. Se você executar seu código por meio de teste, preparação e produção, deverá ter ambientes correspondentes.

Assim como as funções, podemos configurar os arquivos de definição no Ruby DSL ou no JSON.

Em nosso diretório chef-repo em nossa estação de trabalho, devemos ter um diretório de ambientes, onde devemos colocar nossos arquivos de ambiente.

cd ~/chef-repo/environments

Dentro desse diretório, se fôssemos definir um ambiente para desenvolvimento, poderíamos fazer um arquivo assim:

nano development.rb
name "development"
description "The master development branch"
cookbook_versions({
    "nginx" => "<= 1.1.0",
    "apt" => "= 0.0.1"
})
override_attributes ({
    "nginx" => {
        "listen" => [ "80", "443" ]
    },
    "mysql" => {
        "root_pass" => "root"
    }
})

Como você pode ver, uma das principais vantagens de incorporar ambientes em seu sistema é que você pode especificar restrições de versão para os livros de receitas e receitas que são implantados.

Também poderíamos usar o formato JSON. A ferramenta faca pode gerar o modelo de um arquivo de ambiente digitando:

knife environment create development

Isso abrirá nosso editor (novamente, você pode definir seu editor com export EDITOR=nano) com um arquivo de ambiente pré-carregado com o nome preenchido.

Poderíamos criar o mesmo arquivo digitando:

{
  "name": "development",
  "description": "The master development branch",
  "cookbook_versions": {
    "nginx": "<= 1.1.0",
    "apt": "= 0.0.1"
  },
  "json_class": "Cheff:Environment",
  "chef_type": "environment",
  "default_attributes": {
  },
  "override_attributes": {
    "nginx": {
      "listen": [
        "80",
        "443"
      ]
    },
    "mysql": {
      "root_pass": "root"
    }
  }
}

Este arquivo deve ser funcionalmente igual ao arquivo Ruby que demonstramos acima. Assim como os arquivos de função JSON, os arquivos JSON de ambiente têm duas informações extras (json_class e chef_type) que devem ser deixadas em paz.

Movendo arquivos de ambiente de e para o servidor

Neste ponto, se você usou o Ruby DSL, seu arquivo está na estação de trabalho e se você usou JSON, seu arquivo está apenas no servidor. Podemos facilmente mover arquivos para frente e para trás através da faca.

Poderíamos enviar nosso arquivo Ruby para o servidor Chef digitando isto:

knife environment from file ~/chef-repo/environments/development.rb

Para nosso arquivo JSON, podemos retirar o arquivo de ambiente do servidor digitando algo como:

knife environment show development -Fjson > ~/chef-repo/environments/development.json

Isso exibirá o arquivo JSON do servidor e canalizará os resultados para um arquivo local no subdiretório de ambientes.

Configurando ambientes em nós

Cada nó pode estar em exatamente um ambiente. Podemos especificar o ambiente ao qual um nó pertence editando suas informações de nó.

Por exemplo, para editar um nó chamado client1, poderíamos digitar isto:

knife node edit client1

Isso abrirá um arquivo formatado em JSON com os parâmetros do nó atual:

{
  "name": "client1",
  "chef_environment": "_default",
  "normal": {
    "tags": [

    ]
  },
  "run_list": [
    "role[web_server]"
  ]
}

Como você pode ver, o chef_environment está definido como _default originalmente. Podemos simplesmente modificar esse valor para colocar o nó em um novo ambiente.

Quando terminar, salve e feche o arquivo. Na próxima execução chef-client no nó, ele selecionará os novos atributos e as restrições de versão e se modificará para se alinhar à nova política.

Conclusão

Até agora, você deve ter um bom entendimento das diferentes maneiras de trabalhar com funções e ambientes para solidificar o estado em que suas máquinas devem estar. Usando essas estratégias de categorização, você pode começar a gerenciar a maneira como o Chef trata os servidores em diferentes contextos.

Por Justin Ellingwood

Artigos relacionados: