Konfigurera en server för att distribuera en Rails-applikation med Ansible

För inte så länge sedan behövde jag skriva flera Ansible-spelböcker för att förbereda servern för att distribuera en Rails-applikation. Och överraskande nog hittade jag inte en enkel steg-för-steg-manual. Jag ville inte kopiera någon annans spelbok utan att förstå vad som hände, och till slut var jag tvungen att läsa dokumentationen och samla ihop allt själv. Jag kanske kan hjälpa någon att påskynda denna process med hjälp av den här artikeln.

Det första du bör förstå är att ansible ger dig ett bekvämt gränssnitt för att utföra en fördefinierad lista över åtgärder på en eller flera fjärrservrar via SSH. Det finns ingen magi här, du kan inte installera ett plugin och få en driftstopp utan driftstopp av din applikation med docker, övervakning och andra godsaker ur lådan. För att kunna skriva en lekbok måste du veta exakt vad du vill göra och hur du gör det. Det är därför jag inte är nöjd med färdiga spelböcker från GitHub, eller artiklar som: "Kopiera och kör, det kommer att fungera."

Vad behöver vi?

Som jag redan sa, för att skriva en lekbok måste du veta vad du vill göra och hur du gör det. Låt oss bestämma vad vi behöver. För en Rails-applikation kommer vi att behöva flera systempaket: nginx, postgresql (redis, etc). Dessutom behöver vi en specifik version av rubin. Det är bäst att installera det via rbenv (rvm, asdf...). Att köra allt detta som en root-användare är alltid en dålig idé, så du måste skapa en separat användare och konfigurera hans rättigheter. Efter detta måste du ladda upp vår kod till servern, kopiera konfigurationerna för nginx, postgres, etc och starta alla dessa tjänster.

Som ett resultat är sekvensen av åtgärder som följer:

  1. Logga in som root
  2. installera systempaket
  3. skapa en ny användare, konfigurera rättigheter, ssh-nyckel
  4. konfigurera systempaket (nginx etc) och kör dem
  5. Vi skapar en användare i databasen (du kan direkt skapa en databas)
  6. Logga in som ny användare
  7. Installera rbenv och ruby
  8. Installation av buntaren
  9. Laddar upp applikationskoden
  10. Startar Puma-servern

Dessutom kan de sista stegen göras med capistrano, åtminstone ur lådan kan den kopiera kod till utgivningskataloger, byta utgåva med en symbolisk länk vid lyckad distribution, kopiera konfigurationer från en delad katalog, starta om puma, etc. Allt detta kan göras med Ansible, men varför?

Filstruktur

Ansible har strikt filstruktur för alla dina filer, så det är bäst att ha allt i en separat katalog. Dessutom är det inte så viktigt om det kommer att vara i själva rälsapplikationen eller separat. Du kan lagra filer i ett separat git-förråd. Personligen tyckte jag att det var mest bekvämt att skapa en lämplig katalog i katalogen /config i rails-applikationen och lagra allt i ett arkiv.

Enkel lekbok

Playbook är en yml-fil som med hjälp av speciell syntax beskriver vad Ansible ska göra och hur. Låt oss skapa den första lekboken som inte gör något:

---
- name: Simple playbook
  hosts: all

Här säger vi helt enkelt att vår lekbok heter Simple Playbook och att dess innehåll ska köras för alla värdar. Vi kan spara den i katalogen /ansible med namnet playbook.yml och försök köra:

ansible-playbook ./playbook.yml

PLAY [Simple Playbook] ************************************************************************************************************************************
skipping: no hosts matched

Ansible säger att den inte känner till några värdar som matchar alla-listan. De måste anges i en special inventeringsfil.

Låt oss skapa den i samma ansible katalog:

123.123.123.123

Så här anger vi helt enkelt värden (helst värden för vår VPS för testning, eller så kan du registrera lokalvärd) och sparar den under namnet inventory.
Du kan prova att köra ansible med en inventeringsfil:

ansible-playbook ./playbook.yml -i inventory
PLAY [Simple Playbook] ************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************

PLAY RECAP ************************************************************************************************************************************

Om du har ssh-åtkomst till den angivna värden kommer ansible att ansluta och samla in information om fjärrsystemet. (standard TASK [Gathing Facts]) varefter den kommer att ge en kort rapport om utförandet (PLAY RECAP).

Som standard använder anslutningen det användarnamn under vilket du är inloggad i systemet. Det kommer troligen inte att finnas på värden. I playbook-filen kan du ange vilken användare som ska användas för att ansluta med hjälp av remote_user-direktivet. Dessutom kan information om ett fjärrsystem ofta vara onödig för dig och du bör inte slösa tid på att samla in den. Den här uppgiften kan också inaktiveras:

---
- name: Simple playbook
  hosts: all
  remote_user: root
  become: true
  gather_facts: no

Försök att köra spelboken igen och se till att anslutningen fungerar. (Om du angav root-användaren, måste du också ange direktivet become: true för att få förhöjda rättigheter. Som skrivet i dokumentationen: become set to ‘true’/’yes’ to activate privilege escalation. även om det inte är helt klart varför).

Kanske kommer du att få ett fel som orsakats av det faktum att ansible inte kan fastställa Python-tolken, då kan du specificera det manuellt:

ansible_python_interpreter: /usr/bin/python3 

Du kan ta reda på var du har python med kommandot whereis python.

Installera systempaket

Ansibles standarddistribution innehåller många moduler för att arbeta med olika systempaket, så vi behöver inte skriva bash-skript av någon anledning. Nu behöver vi en av dessa moduler för att uppdatera systemet och installera systempaket. Jag har Ubuntu Linux på min VPS, så för att installera paket använder jag apt-get и modul för det. Om du använder ett annat operativsystem kan du behöva en annan modul (kom ihåg att jag sa i början att vi måste veta i förväg vad och hur vi ska göra). Syntaxen kommer dock med största sannolikhet att vara liknande.

Låt oss komplettera vår lekbok med de första uppgifterna:

---
- name: Simple playbook
  hosts: all
  remote_user: root
  become: true
  gather_facts: no

  tasks:
    - name: Update system
      apt: update_cache=yes
    - name: Install system dependencies
      apt:
        name: git,nginx,redis,postgresql,postgresql-contrib
        state: present

Task är exakt den uppgift som Ansible kommer att utföra på fjärrservrar. Vi ger uppgiften ett namn så att vi kan spåra dess utförande i loggen. Och vi beskriver, med hjälp av syntaxen för en specifik modul, vad den behöver göra. I detta fall apt: update_cache=yes - säger att man ska uppdatera systempaket med apt-modulen. Det andra kommandot är lite mer komplicerat. Vi skickar en lista med paket till apt-modulen och säger att de är det state borde bli present, det vill säga vi säger installera dessa paket. På liknande sätt kan vi säga åt dem att ta bort dem, eller uppdatera dem genom att helt enkelt ändra state. Observera att för att rails ska fungera med postgresql behöver vi postgresql-contrib-paketet, som vi installerar nu. Återigen, du behöver veta och göra detta, ansible på egen hand kommer inte att göra detta.

Försök att köra spelboken igen och kontrollera att paketen är installerade.

Skapar nya användare.

För att arbeta med användare har Ansible även en modul - användare. Låt oss lägga till ytterligare en uppgift (jag gömde de redan kända delarna av spelboken bakom kommentarerna för att inte kopiera den helt varje gång):

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Add a new user
      user:
        name: my_user
        shell: /bin/bash
        password: "{{ 123qweasd | password_hash('sha512') }}"

Vi skapar en ny användare, ställer in ett schema och lösenord för den. Och då stöter vi på flera problem. Vad händer om användarnamn måste vara olika för olika värdar? Och att lagra lösenordet i klartext i spelboken är en mycket dålig idé. Till att börja med, låt oss lägga in användarnamnet och lösenordet i variabler, och mot slutet av artikeln kommer jag att visa hur man krypterar lösenordet.

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Add a new user
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"

Variabler ställs in i playbooks med dubbla lockiga hängslen.

Vi kommer att ange värdena för variablerna i inventeringsfilen:

123.123.123.123

[all:vars]
user=my_user
user_password=123qweasd

Observera direktivet [all:vars] - det står att nästa textblock är variabler (vars) och de är tillämpliga på alla värdar (alla).

Designen är också intressant "{{ user_password | password_hash('sha512') }}". Grejen är att ansible inte installerar användaren via user_add som du skulle göra det manuellt. Och det sparar all data direkt, varför vi också måste konvertera lösenordet till en hash i förväg, vilket är vad det här kommandot gör.

Låt oss lägga till vår användare i sudo-gruppen. Men innan detta måste vi se till att en sådan grupp existerar eftersom ingen kommer att göra detta åt oss:

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Ensure a 'sudo' group
      group:
        name: sudo
        state: present
    - name: Add a new user
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"
        groups: "sudo"

Allt är ganska enkelt, vi har även en gruppmodul för att skapa grupper, med en syntax väldigt lik apt. Då räcker det med att registrera denna grupp till användaren (groups: "sudo").
Det är också användbart att lägga till en ssh-nyckel till den här användaren så att vi kan logga in med den utan lösenord:

---
- name: Simple playbook
  # ...
  tasks:
    # ...
    - name: Ensure a 'sudo' group
      group:
      name: sudo
        state: present
    - name: Add a new user
      user:
        name: "{{ user }}"
        shell: /bin/bash
        password: "{{ user_password | password_hash('sha512') }}"
        groups: "sudo"
    - name: Deploy SSH Key
      authorized_key:
        user: "{{ user }}"
        key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
        state: present

I det här fallet är designen intressant "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" — den kopierar innehållet i filen id_rsa.pub (ditt namn kan vara annorlunda), det vill säga den offentliga delen av ssh-nyckeln och laddar upp den till listan över auktoriserade nycklar för användaren på servern.

roll

Alla tre uppgifterna för att skapa användning kan enkelt klassificeras i en grupp av uppgifter, och det skulle vara en bra idé att lagra denna grupp separat från huvudspelboken så att den inte blir för stor. För detta ändamål har Ansible roller.
Enligt filstrukturen som anges i början måste roller placeras i en separat rollkatalog, för varje roll finns det en separat katalog med samma namn, inuti katalogen för uppgifter, filer, mallar etc.
Låt oss skapa en filstruktur: ./ansible/roles/user/tasks/main.yml (main är huvudfilen som kommer att laddas och köras när en roll ansluts till spelboken; andra rollfiler kan kopplas till den). Nu kan du överföra alla uppgifter relaterade till användaren till denna fil:

# Create user and add him to groups
- name: Ensure a 'sudo' group
  group:
    name: sudo
    state: present

- name: Add a new user
  user:
    name: "{{ user }}"
    shell: /bin/bash
    password: "{{ user_password | password_hash('sha512') }}"
    groups: "sudo"

- name: Deploy SSH Key
  authorized_key:
    user: "{{ user }}"
    key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
    state: present

I huvudspelboken måste du ange för att använda användarrollen:

---
- name: Simple playbook
  hosts: all
  remote_user: root
  gather_facts: no

  tasks:
    - name: Update system
      apt: update_cache=yes
    - name: Install system dependencies
      apt:
        name: git,nginx,redis,postgresql,postgresql-contrib
        state: present

  roles:
    - user

Det kan också vara vettigt att uppdatera systemet före alla andra uppgifter; för att göra detta kan du byta namn på blocket tasks där de definieras i pre_tasks.

Konfigurera nginx

Vi borde redan ha Nginx installerat; vi måste konfigurera det och köra det. Låt oss göra det direkt i rollen. Låt oss skapa en filstruktur:

- ansible
  - roles
    - nginx
      - files
      - tasks
        - main.yml
      - templates

Nu behöver vi filer och mallar. Skillnaden mellan dem är att ansible kopierar filerna direkt, som de är. Och mallar måste ha tillägget j2 och de kan använda variabelvärden med samma dubbla hängslen.

Låt oss aktivera nginx main.yml fil. För detta har vi en systemmodul:

# Copy nginx configs and start it
- name: enable service nginx and start
  systemd:
    name: nginx
    state: started
    enabled: yes

Här säger vi inte bara att nginx måste startas (det vill säga vi lanserar det), utan vi säger direkt att det måste vara aktiverat.
Låt oss nu kopiera konfigurationsfilerna:

# Copy nginx configs and start it
- name: enable service nginx and start
  systemd:
    name: nginx
    state: started
    enabled: yes

- name: Copy the nginx.conf
  copy:
    src: nginx.conf
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
    backup: yes

- name: Copy template my_app.conf
  template:
    src: my_app_conf.j2
    dest: /etc/nginx/sites-available/my_app.conf
    owner: root
    group: root
    mode: '0644'

Vi skapar den huvudsakliga nginx-konfigurationsfilen (du kan ta den direkt från servern eller skriva den själv). Och även konfigurationsfilen för vår applikation i katalogen sites_available (detta är inte nödvändigt men användbart). I det första fallet använder vi kopieringsmodulen för att kopiera filer (filen måste finnas i /ansible/roles/nginx/files/nginx.conf). I den andra kopierar vi mallen och ersätter variablernas värden. Mallen ska finnas i /ansible/roles/nginx/templates/my_app.j2). Och det kan se ut ungefär så här:

upstream {{ app_name }} {
  server unix:{{ app_path }}/shared/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name {{ server_name }} {{ inventory_hostname }};
  root {{ app_path }}/current/public;

  try_files $uri/index.html $uri.html $uri @{{ app_name }};
  ....
}

Var uppmärksam på insatserna {{ app_name }}, {{ app_path }}, {{ server_name }}, {{ inventory_hostname }} — dessa är alla variabler vars värden Ansible kommer att ersätta i mallen innan kopiering. Detta är användbart om du använder en spelbok för olika grupper av värdar. Till exempel kan vi lägga till vår inventeringsfil:

[production]
123.123.123.123

[staging]
231.231.231.231

[all:vars]
user=my_user
user_password=123qweasd

[production:vars]
server_name=production
app_path=/home/www/my_app
app_name=my_app

[staging:vars]
server_name=staging
app_path=/home/www/my_stage
app_name=my_stage_app

Om vi ​​nu startar vår spelbok kommer den att utföra de angivna uppgifterna för båda värdarna. Men samtidigt, för en iscensättningsvärd, kommer variablerna att skilja sig från produktionsvariablerna, och inte bara i roller och spelböcker, utan även i nginx-konfigurationer. {{ inventory_hostname }} behöver inte anges i inventeringsfilen - detta speciell ansibel variabel och den värd som spelboken körs för för närvarande lagras där.
Om du vill ha en inventeringsfil för flera värdar, men bara köra för en grupp, kan detta göras med följande kommando:

ansible-playbook -i inventory ./playbook.yml -l "staging"

Ett annat alternativ är att ha separata inventeringsfiler för olika grupper. Eller så kan du kombinera de två tillvägagångssätten om du har många olika värdar.

Låt oss gå tillbaka till att ställa in nginx. Efter att ha kopierat konfigurationsfilerna måste vi skapa en symbollänk i sitest_enabled till my_app.conf från sites_available. Och starta om nginx.

... # old code in mail.yml

- name: Create symlink to sites-enabled
  file:
    src: /etc/nginx/sites-available/my_app.conf
    dest: /etc/nginx/sites-enabled/my_app.conf
    state: link

- name: restart nginx
  service:
    name: nginx
    state: restarted

Allt är enkelt här - återigen möjliga moduler med en ganska standardsyntax. Men det finns en poäng. Det är ingen idé att starta om nginx varje gång. Har du märkt att vi inte skriver kommandon som: "gör så här", syntaxen ser mer ut som "det här borde ha detta tillstånd". Och oftast är det precis så ansible fungerar. Om gruppen redan finns, eller om systempaketet redan är installerat, kommer ansible att kontrollera detta och hoppa över uppgiften. Filer kommer inte heller att kopieras om de helt matchar det som redan finns på servern. Vi kan dra nytta av detta och starta om nginx endast om konfigurationsfilerna har ändrats. Det finns ett registerdirektiv för detta:

# Copy nginx configs and start it
- name: enable service nginx and start
  systemd:
    name: nginx
    state: started
    enabled: yes

- name: Copy the nginx.conf
  copy:
    src: nginx.conf
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
    backup: yes
  register: restart_nginx

- name: Copy template my_app.conf
  template:
    src: my_app_conf.j2
    dest: /etc/nginx/sites-available/my_app.conf
    owner: root
    group: root
    mode: '0644'
  register: restart_nginx

- name: Create symlink to sites-enabled
  file:
    src: /etc/nginx/sites-available/my_app.conf
    dest: /etc/nginx/sites-enabled/my_app.conf
    state: link

- name: restart nginx
  service:
    name: nginx
    state: restarted
  when: restart_nginx.changed

Om en av konfigurationsfilerna ändras kommer en kopia att göras och variabeln registreras restart_nginx. Och endast om denna variabel har registrerats kommer tjänsten att startas om.

Och naturligtvis måste du lägga till nginx-rollen i huvudspelboken.

Konfigurera postgresql

Vi måste aktivera postgresql med systemd på samma sätt som vi gjorde med nginx, och även skapa en användare som vi kommer att använda för att komma åt databasen och själva databasen.
Låt oss skapa en roll /ansible/roles/postgresql/tasks/main.yml:

# Create user in postgresql
- name: enable postgresql and start
  systemd:
    name: postgresql
    state: started
    enabled: yes

- name: Create database user
  become_user: postgres
  postgresql_user:
    name: "{{ db_user }}"
    password: "{{ db_password }}"
    role_attr_flags: SUPERUSER

- name: Create database
  become_user: postgres
  postgresql_db:
    name: "{{ db_name }}"
    encoding: UTF-8
    owner: "{{ db_user }}"

Jag kommer inte att beskriva hur man lägger till variabler till inventering, detta har redan gjorts många gånger, liksom syntaxen för modulerna postgresql_db och postgresql_user. Mer information finns i dokumentationen. Det mest intressanta direktivet här är become_user: postgres. Faktum är att som standard är det bara postgres-användaren som har tillgång till postgresql-databasen och endast lokalt. Detta direktiv tillåter oss att utföra kommandon på uppdrag av denna användare (om vi har tillgång, naturligtvis).
Du kan också behöva lägga till en rad till pg_hba.conf för att ge en ny användare tillgång till databasen. Detta kan göras på samma sätt som vi ändrade nginx-konfigurationen.

Och naturligtvis måste du lägga till postgresql-rollen i huvudspelboken.

Installera ruby ​​via rbenv

Ansible har inga moduler för att arbeta med rbenv, men den installeras genom att klona ett git-förråd. Därför blir detta problem det mest icke-standardiserade problemet. Låt oss skapa en roll för henne /ansible/roles/ruby_rbenv/main.yml och låt oss börja fylla i det:

# Install rbenv and ruby
- name: Install rbenv
  become_user: "{{ user }}"
  git: repo=https://github.com/rbenv/rbenv.git dest=~/.rbenv

Vi använder återigen direktivet become_user för att arbeta under användaren vi skapade för dessa ändamål. Eftersom rbenv är installerat i sin hemkatalog, och inte globalt. Och vi använder också git-modulen för att klona förvaret och specificera repo och dest.

Därefter måste vi registrera rbenv init i bashrc och lägga till rbenv till PATH där. För detta har vi lineinfile-modulen:

- name: Add rbenv to PATH
  become_user: "{{ user }}"
  lineinfile:
    path: ~/.bashrc
    state: present
    line: 'export PATH="${HOME}/.rbenv/bin:${PATH}"'

- name: Add rbenv init to bashrc
  become_user: "{{ user }}"
  lineinfile:
    path: ~/.bashrc
    state: present
    line: 'eval "$(rbenv init -)"'

Sedan måste du installera ruby_build:

- name: Install ruby-build
  become_user: "{{ user }}"
  git: repo=https://github.com/rbenv/ruby-build.git dest=~/.rbenv/plugins/ruby-build

Och slutligen installera ruby. Detta görs genom rbenv, det vill säga helt enkelt med bash-kommandot:

- name: Install ruby
  become_user: "{{ user }}"
  shell: |
    export PATH="${HOME}/.rbenv/bin:${PATH}"
    eval "$(rbenv init -)"
    rbenv install {{ ruby_version }}
  args:
    executable: /bin/bash

Vi säger vilket kommando vi ska utföra och med vad. Men här stöter vi på det faktum att ansible inte kör koden som finns i bashrc innan kommandona körs. Detta innebär att rbenv måste definieras direkt i samma skript.

Nästa problem beror på det faktum att skalkommandot inte har något tillstånd ur en ansible synvinkel. Det vill säga, det kommer inte att göras någon automatisk kontroll om denna version av ruby ​​är installerad eller inte. Vi kan göra detta själva:

- name: Install ruby
  become_user: "{{ user }}"
  shell: |
    export PATH="${HOME}/.rbenv/bin:${PATH}"
    eval "$(rbenv init -)"
    if ! rbenv versions | grep -q {{ ruby_version }}
      then rbenv install {{ ruby_version }} && rbenv global {{ ruby_version }}
    fi
  args:
    executable: /bin/bash

Allt som återstår är att installera bundler:

- name: Install bundler
  become_user: "{{ user }}"
  shell: |
    export PATH="${HOME}/.rbenv/bin:${PATH}"
    eval "$(rbenv init -)"
    gem install bundler

Och återigen, lägg till vår roll ruby_rbenv i huvudspelboken.

Delade filer.

I allmänhet kan installationen slutföras här. Sedan återstår bara att köra capistrano och det kommer att kopiera själva koden, skapa de nödvändiga katalogerna och starta applikationen (om allt är korrekt konfigurerat). Capistrano kräver dock ofta ytterligare konfigurationsfiler, som t.ex database.yml eller .env De kan kopieras precis som filer och mallar för nginx. Det finns bara en subtilitet. Innan du kopierar filer måste du skapa en katalogstruktur för dem, ungefär så här:

# Copy shared files for deploy
- name: Ensure shared dir
  become_user: "{{ user }}"
  file:
    path: "{{ app_path }}/shared/config"
    state: directory

vi anger endast en katalog och ansible kommer automatiskt att skapa en överordnad katalog vid behov.

Ansible Vault

Vi har redan stött på det faktum att variabler kan innehålla hemliga data som användarens lösenord. Om du har skapat .env fil för ansökan, och database.yml då måste det finnas ännu fler sådana kritiska data. Det skulle vara bra att dölja dem för nyfikna ögon. För detta ändamål används den ansible valv.

Låt oss skapa en fil för variabler /ansible/vars/all.yml (här kan du skapa olika filer för olika grupper av värdar, precis som i inventeringsfilen: production.yml, staging.yml, etc).
Alla variabler som måste krypteras måste överföras till denna fil med standard yml-syntax:

# System vars
user_password: 123qweasd
db_password: 123qweasd

# ENV vars
aws_access_key_id: xxxxx
aws_secret_access_key: xxxxxx
aws_bucket: bucket_name
rails_secret_key_base: very_secret_key_base

Därefter kan den här filen krypteras med kommandot:

ansible-vault encrypt ./vars/all.yml

När du krypterar måste du naturligtvis ställa in ett lösenord för dekryptering. Du kan se vad som kommer att finnas inuti filen efter att ha anropat det här kommandot.

Genom ansible-vault decrypt filen kan dekrypteras, modifieras och sedan krypteras igen.

Du behöver inte dekryptera filen för att fungera. Du lagrar det krypterat och kör spelboken med argumentet --ask-vault-pass. Ansible kommer att fråga efter lösenordet, hämta variablerna och utföra uppgifterna. All data kommer att förbli krypterad.

Det fullständiga kommandot för flera grupper av värdar och eventuella valv kommer att se ut ungefär så här:

ansible-playbook -i inventory ./playbook.yml -l "staging" --ask-vault-pass

Men jag kommer inte att ge dig hela texten av spelböcker och roller, skriv det själv. För ansible är så - om du inte förstår vad som behöver göras, kommer det inte att göra det åt dig.

Källa: will.com

Lägg en kommentar