Nexus: установка, запуск, деплой в репозиторий + NGINX и SSL

Автор: | 27/09/2018

У Android-команды поломался “деплой” через отправку письма с вложением на Gmail (было сделано ещё до меня), и появилась необходимость быстренько “накостылить” репозиторий.

Планировался он давно, но сейчас будет без всякой автоматизации – просто руками поднять, запустить, что бы они могли деплоить.

Использовать будем free-версию Sonatype Nexus, запускать будем на AWS EC2, из Docker-образа, бекенд будет в AWS S3 корзине.

Запуск Nexus из Docker

Обновляем систему, устанавливаем NGINX, и пока его стопаем:

[simterm]

root@ip-172-31-36-107:/home/admin# apt update && apt -y upgrade && apt -y install nginx
root@ip-172-31-36-107:/home/admin# service nginx stop

[/simterm]

Устанавливаем Docker:

[simterm]

root@ip-172-31-36-107:/home/admin# curl https://get.docker.com/ | bash

[/simterm]

И Docker Compose:

[simterm]

root@ip-172-31-36-107:/home/admin# curl -L "https://github.com/docker/compose/releases/download/1.22.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
root@ip-172-31-36-107:/home/admin# chmod +x /usr/local/bin/docker-compose

[/simterm]

Делаем тестовый запуск Nexus:

[simterm]

root@ip-172-31-36-107:/home/admin# docker run -ti -p 8081:8081 sonatype/nexus3
Unable to find image 'sonatype/nexus3:latest' locally
latest: Pulling from sonatype/nexus3
...
2018-09-27 10:18:09,347+0000 INFO  [FelixStartLevel] *SYSTEM org.sonatype.nexus.bootstrap.ConfigurationBuilder -   application-host='0.0.0.0'
2018-09-27 10:18:09,348+0000 INFO  [FelixStartLevel] *SYSTEM org.sonatype.nexus.bootstrap.ConfigurationBuilder -   application-port='8081'
...
Started Sonatype Nexus OSS 3.13.0-01
...

[/simterm]

Проверяем:

Дефолтный логин-пасс – admin:admin123, можно зайти, посмотреть как всё выглядит под админом:

Добавление репозитория

Добавляем репозиторий – переходим в Repositories:

Кликаем Create repository, выбираем raw(hosted) (см. описание тут>>>):

API

Попробуем в него загрузить файл.

Деплой из Jenkins будет curl-ом через Nexus API, для работы с которым есть отличный конструктор запросов:

Выбираем POST /v1/components, жмём Try it out, указываем что и куда будем загружать, нам тут интересен Request URL:

Загружаем файл curl-ом:

[simterm]

$ curl -v -u admin:admin123 --upload-file nexus-test.txt http://54.154.29.22:8081/repository/test/nexus-test.txt
*   Trying 54.154.29.22...
* TCP_NODELAY set
* Connected to 54.154.29.22 (54.154.29.22) port 8081 (#0)
* Server auth using Basic with user 'admin'
> PUT /repository/test/nexus-test.txt HTTP/1.1
> Host: 54.154.29.22:8081
> Authorization: Basic YWRtaW46YWRtaW4xMjM=
> User-Agent: curl/7.61.1
> Accept: */*
> Content-Length: 0
> 
< HTTP/1.1 201 Created
< Date: Thu, 27 Sep 2018 10:53:33 GMT
< Server: Nexus/3.13.0-01 (OSS)
< X-Content-Type-Options: nosniff
< Content-Security-Policy: sandbox allow-forms allow-modals allow-popups allow-presentation allow-scripts allow-top-navigation
< Content-Length: 0
< 
* Connection #0 to host 54.154.29.22 left intact

[/simterm]

Проверяем:

[simterm]

$ curl -X GET "http://54.154.29.22:8081/service/rest/v1/components?repository=test" -H "accept: application/json"
{
  "items" : [ {
    "id" : "dGVzdDpkMzUxMGI3YWQxOGQ4MmNkZTU2M2ExZWUxYWY5YjAwZA",
    "repository" : "test",
    "format" : "raw",
    "group" : "/",
    "name" : "nexus-test.txt",
    "version" : null,
    "assets" : [ {
      "downloadUrl" : "http://54.154.29.22:8081/repository/test/nexus-test.txt",
      "path" : "nexus-test.txt",
      "id" : "dGVzdDpjNzcwMTY5YzBiMmUzZWQ4ZWYzNTZhYTRlOWIyZmU0Ng",
      "repository" : "test",
      "format" : "raw",
      "checksum" : {
        "sha1" : "da39a3ee5e6b4b0d3255bfef95601890afd80709",
        "md5" : "d41d8cd98f00b204e9800998ecf8427e"
      }
    } ]
  } ],
  "continuationToken" : null
}

[/simterm]

ОК, тут всё работает.

Файл настроек

Дефолтный файл настроек – nexus.properties.

Находим имя контейнера:

[simterm]

# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
17ca436cd5b0        sonatype/nexus3     "sh -c ${SONATYPE_DI…"   About an hour ago   Up About an hour    0.0.0.0:8081->8081/tcp   laughing_babbage

[/simterm]

Подключаемся к нему:

[simterm]

root@ip-172-31-36-107:/home/admin# docker exec -ti laughing_babbage  bash

[/simterm]

Находим файл:

[simterm]

bash-4.2$ find / -name nexus.properties -type f
find: '/root': Permission denied
find: '/var/lib/machines': Permission denied
find: '/var/lib/yum/history/2018-08-04/1': Permission denied
find: '/var/lib/yum/history/2018-08-04/2': Permission denied
find: '/var/cache/ldconfig': Permission denied
find: '/proc/tty/driver': Permission denied
/nexus-data/etc/nexus.properties

[/simterm]

И проверяем содержимое:

[simterm]

bash-4.2$ cat /nexus-data/etc/nexus.properties
# Jetty section
# application-port=8081
# application-host=0.0.0.0
# nexus-args=${jetty.etc}/jetty.xml,${jetty.etc}/jetty-http.xml,${jetty.etc}/jetty-requestlog.xml
# nexus-context-path=/${NEXUS_CONTEXT}

# Nexus section
# nexus-edition=nexus-pro-edition
# nexus-features=\
#  nexus-pro-feature
# nexus.clustered=false

[/simterm]

И все данные Nexus-а – потом будем подключать с внешнего каталога в контейнер:

[simterm]

bash-4.2$ ls -l /nexus-data/
total 68
drwxr-xr-x   3 nexus nexus  4096 Sep 27 10:21 blobs
drwxr-xr-x 271 nexus nexus 12288 Sep 27 10:20 cache
drwxr-xr-x   9 nexus nexus  4096 Sep 27 10:21 db
drwxr-xr-x   3 nexus nexus  4096 Sep 27 10:21 elasticsearch
drwxr-xr-x   3 nexus nexus  4096 Sep 27 10:20 etc
drwxr-xr-x   2 nexus nexus  4096 Sep 27 10:20 generated-bundles
drwxr-xr-x   2 nexus nexus  4096 Sep 27 10:20 instances
drwxr-xr-x   3 nexus nexus  4096 Sep 27 10:20 javaprefs
drwxr-xr-x   3 nexus nexus  4096 Sep 27 10:20 keystores
-rw-r--r--   1 nexus nexus    14 Sep 27 10:20 lock
drwxr-xr-x   2 nexus nexus  4096 Sep 27 10:21 log
drwxr-xr-x   2 nexus nexus  4096 Sep 27 10:20 orient
-rw-r--r--   1 nexus nexus     5 Sep 27 10:20 port
drwxr-xr-x   2 nexus nexus  4096 Sep 27 10:21 restore-from-backup
drwxr-xr-x   7 nexus nexus  4096 Sep 27 10:55 tmp

[/simterm]

Авторизация

Сейчас Nexus разрешает загрузку из репозитория без авторизации – это плохо.

Отключаем в Anonymous:

Проверяем:

[simterm]

$ wget http://54.154.29.22:8081/repository/test/android/1.0/debug-v.2.4.0-1-BetaStage.apk
--2018-09-27 14:21:42--  http://54.154.29.22:8081/repository/test/android/1.0/debug-v.2.4.0-1-BetaStage.apk
Connecting to 54.154.29.22:8081... connected.
HTTP request sent, awaiting response... 401 Unauthorized

Username/Password Authentication Failed.

[/simterm]

S3 backend и Blob store

Последний штрих – проверить работу с AWS S3, который у нас будет использоваться для хранения данных.

Создаём корзину:

Создаём политику для доступа к корзине:

Добавляем IAM пользователя:

Подключаем ему политику:

Возвращаемся к Nexus, настраиваем новый Blob Store:

Выбираем тип S3, заполняем поля:

Добавление репозитория

Добавляем репозиторий, тот же raw (hosted), но теперь выбираем новое хранилище:

Пробуем запостить туда файл:

[simterm]

$ curl -v -u admin:admin123 --upload-file debug-v.2.4.0-1-BetaStage.apk http://54.154.29.22:8081/repository/android/debug-v.2.4.0-1-BetaStage.apk
*   Trying 54.154.29.22...
* TCP_NODELAY set
* Connected to 54.154.29.22 (54.154.29.22) port 8081 (#0)
* Server auth using Basic with user 'admin'
> PUT /repository/android/debug-v.2.4.0-1-BetaStage.apk HTTP/1.1
> Host: 54.154.29.22:8081
> Authorization: Basic YWRtaW46YWRtaW4xMjM=
> User-Agent: curl/7.61.1
> Accept: */*
> Content-Length: 14110446
> Expect: 100-continue
> 
< HTTP/1.1 100 Continue
* We are completely uploaded and fine
< HTTP/1.1 201 Created
< Date: Thu, 27 Sep 2018 11:33:11 GMT
< Server: Nexus/3.13.0-01 (OSS)
< X-Content-Type-Options: nosniff
< Content-Security-Policy: sandbox allow-forms allow-modals allow-popups allow-presentation allow-scripts allow-top-navigation
< Content-Length: 0
< 
* Connection #0 to host 54.154.29.22 left intact

[/simterm]

Проверяем корзину:

[simterm]

$ aws --profile bm-backend s3 ls s3://bttrm-nexus-repo
                           PRE content/
2018-09-27 14:33:19         61 EC08C25B-21CD7317-B8204C96-6480D680-E92F7389-metrics.properties
2018-09-27 14:31:11         40 metadata.properties

[/simterm]

Отлично – Nexus сохранил данные в корзину, всё работает.

Compose и persistent-data

Теперь добавим Compose-файл, который будет запускать Nexus и подключить каталог с данными с хоста.

Создаём новый EBS, подключаем его к EC2:

[simterm]

root@ip-172-31-36-107:/home/admin# lsblk 
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0   8G  0 disk 
└─xvda1 202:1    0   8G  0 part /
xvdf    202:80   0  20G  0 disk

[/simterm]

Создаём на нём раздел:

[simterm]

root@ip-172-31-36-107:/home/admin# fdisk /dev/xvdf 

Welcome to fdisk (util-linux 2.29.2).                                                                                                                                                                                                         
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0x7d091a57.

Command (m for help): p
Disk /dev/xvdf: 20 GiB, 21474836480 bytes, 41943040 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x7d091a57

Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p): 

Using default response p.
Partition number (1-4, default 1): 
First sector (2048-41943039, default 2048): 
Last sector, +sectors or +size{K,M,G,T,P} (2048-41943039, default 41943039): 

Created a new partition 1 of type 'Linux' and of size 20 GiB.

Command (m for help): p
Disk /dev/xvdf: 20 GiB, 21474836480 bytes, 41943040 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x7d091a57

Device     Boot Start      End  Sectors Size Id Type
/dev/xvdf1       2048 41943039 41940992  20G 83 Linux

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.

[/simterm]

Создаём файловую систему на /dev/xvdf1:

[simterm]

root@ip-172-31-36-107:/home/admin# mkfs.ext4 /dev/xvdf1
mke2fs 1.43.4 (31-Jan-2017)
Creating filesystem with 5242624 4k blocks and 1310720 inodes
Filesystem UUID: a0250cb9-b9c7-4ca7-b144-83d8c00a00a5
Superblock backups stored on blocks: 
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 
        4096000

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: don

[/simterm]

Получаем ID раздела:

[simterm]

root@ip-172-31-36-107:/home/admin# blkid /dev/xvdf1
/dev/xvdf1: UUID="a0250cb9-b9c7-4ca7-b144-83d8c00a00a5" TYPE="ext4" PARTUUID="7d091a57-01"

[/simterm]

Добавляем его в /etc/fstab:

[simterm]

root@ip-172-31-36-107:/home/admin# cat /etc/fstab
# /etc/fstab: static file system information.
#
# <file sys>    <mount point>   <type>  <options>       <dump>  <pass>

# device during installation: /dev/loop0p1
UUID=b524f8c0-90e7-4fc7-a842-6cb2380086c8       /       ext4    rw,discard,errors=remount-ro    0       1
# /nexus-data
UUID="a0250cb9-b9c7-4ca7-b144-83d8c00a00a5" /nexus-data ext4 defaults        0       2

[/simterm]

Создаём каталог:

[simterm]

root@ip-172-31-36-107:/home/admin# mkdir /nexus-data

[/simterm]

Создаём пользователя:

[simterm]

root@ip-172-31-36-107:/home/admin# adduser nexus --shell=/bin/false --no-create-home --disabled-password
Adding user `nexus' ...
Adding new group `nexus' (1001) ...
Adding new user `nexus' (1001) with group `nexus' ...
Not creating home directory `/home/nexus'.
Changing the user information for nexus
Enter the new value, or press ENTER for the default
        Full Name []: 
        Room Number []: 
        Work Phone []: 
        Home Phone []: 
        Other []: 
Is the information correct? [Y/n] y

[/simterm]

Пользователь в образе Nexus работает UID 200, меняем его для нашего пользователя:

[simterm]

root@ip-172-31-36-107:/home/admin# usermod -u 200 nexus

[/simterm]

И для группы:

[simterm]

root@ip-172-31-36-107:/home/admin# groupmod -g 200 nexus

[/simterm]

Обновляем владельца каталога:

[simterm]

root@ip-172-31-36-107:/home/admin# chown -R nexus:nexus /nexus-data/

[/simterm]

Создаём каталог для Compose-файла:

[simterm]

root@ip-172-31-36-107:/home/admin# mkdir /opt/nexus

[/simterm]

Создаём Compose файл /opt/nexus/nexus-server.yml:

version: '3'

networks:
 nexus:

services:
  nexus:
    user: nexus
    image: sonatype/nexus3
    networks:
      - nexus
    ports:
      - '8081:8081'
    volumes:
      - /nexus-data:/nexus-data
    logging:
      driver: "journald"

Проверяем – запускаем его:

[simterm]

root@ip-172-31-36-107:/opt/nexus# docker-compose -f nexus-server.yml up
Starting nexus_nexus_1 ... done
Attaching to nexus_nexus_1
...

[/simterm]

Создаём systemd-unit файл /etc/systemd/system/nexus.service:

[Unit]
Description=Nexus repository service
Requires=docker.service
After=docker.service

[Service]
Restart=always
WorkingDirectory=/opt/nexus

# Compose up
ExecStart=/usr/local/bin/docker-compose -f nexus-server.yml up

# Compose down, remove containers and volumes
ExecStop=/usr/local/bin/docker-compose -f nexus-server.yml down -v

[Install]
WantedBy=multi-user.target

Проверяем:

[simterm]

root@ip-172-31-36-107:/opt/nexus# systemctl start nexus
root@ip-172-31-36-107:/opt/nexus# systemctl status nexus
● nexus.service - Nexus repository service
   Loaded: loaded (/etc/systemd/system/nexus.service; disabled; vendor preset: enabled)
   Active: active (running) since Thu 2018-09-27 12:41:12 UTC; 1s ago
 Main PID: 21589 (docker-compose)
    Tasks: 4 (limit: 4915)
   Memory: 46.9M
      CPU: 473ms
   CGroup: /system.slice/nexus.service
           ├─21589 /usr/local/bin/docker-compose -f nexus-server.yml up
           └─21590 /usr/local/bin/docker-compose -f nexus-server.yml up

[/simterm]

Логи:

[simterm]

root@ip-172-31-36-107:/opt/nexus# journalctl -u nexus -f           
-- Logs begin at Thu 2018-09-27 09:39:32 UTC. --
Sep 27 12:59:40 ip-172-31-36-107 docker-compose[24884]: nexus_1  |         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
Sep 27 12:59:40 ip-172-31-36-107 docker-compose[24884]: nexus_1  |         at java.lang.Thread.run(Thread.java:748)
Sep 27 12:59:41 ip-172-31-36-107 docker-compose[24884]: nexus_nexus_1 exited with code 0
Sep 27 12:59:41 ip-172-31-36-107 docker-compose[25183]: [71B blob data]
Sep 27 12:59:41 ip-172-31-36-107 docker-compose[25183]: [73B blob data]
Sep 27 12:59:41 ip-172-31-36-107 docker-compose[24884]: Gracefully stopping... (press Ctrl+C again to force)
Sep 27 12:59:41 ip-172-31-36-107 systemd[1]: Stopped Nexus repository service.
Sep 27 12:59:42 ip-172-31-36-107 systemd[1]: Started Nexus repository service.
Sep 27 12:59:42 ip-172-31-36-107 docker-compose[25284]: Creating network "nexus_nexus" with the default driver
Sep 27 12:59:42 ip-172-31-36-107 docker-compose[25284]: Creating nexus_nexus_1 ...
Sep 27 12:59:46 ip-172-31-36-107 docker-compose[25284]: [71B blob data]
Sep 27 12:59:46 ip-172-31-36-107 docker-compose[25284]: nexus_1  | 2018-09-27 12:59:46,173+0000 INFO  [FelixStartLevel] *SYSTEM org.sonatype.nexus.pax.logging.NexusLogActivator - start
...

[/simterm]

Добавляем в автозапуск:

[simterm]

root@ip-172-31-36-107:/opt/nexus# systemctl enable nexus
Created symlink /etc/systemd/system/multi-user.target.wants/nexus.service → /etc/systemd/system/nexus.service.

[/simterm]

Повторяем настройку Blob store, создаём репозиторий, загружаем файл:

[simterm]

$ curl -u admin:admin123 --upload-file debug-v.2.4.0-1-BetaStage.apk http://54.154.29.22:8081/repository/android/debug/debug-v.2.4.0-1-BetaStage.apk

[/simterm]

Проверяем:

NGINX и SSL

Устанавливаем Let’s Encrypt:

[simterm]

# git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt

[/simterm]

Добавляем файл /etc/nginx/conf.d/nexus-repo.domain.world.conf:

upstream nexus {
    server 127.0.0.1:8081;
}

server {

        listen 80;
        server_name nexus-repo.domain.world;

        # Lets Encrypt Webroot
        location ~ /.well-known {
            root /var/www/html;
            allow all;
        }

        location / {
            return 301 https://nexus-repo.domain.world$request_uri;
        }
}

Обновляем /opt/nexus/nexus-server.yml – задаём порт хоста 8081 вместо 80, как было во время тестирования-настройки:

version: '3'

networks:
 nexus:

services:
  nexus:
    user: nexus
    image: sonatype/nexus3
    networks:
      - nexus
    ports:
      - '8081:8081'
    volumes:
      - /nexus-data:/nexus-data
    logging:
      driver: "journald"

Перезапускаем Nexus:

[simterm]

root@ip-172-31-36-107:/etc/nginx# systemctl restart nexus

[/simterm]

Проверяем и перезапускаем NGINX:

[simterm]

root@ip-172-31-36-107:/etc/nginx# nginx -t && service nginx restart
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

[/simterm]

Получаем сертификат:

[simterm]

root@ip-172-31-36-107:/etc/nginx# /opt/letsencrypt/letsencrypt-auto certonly -a webroot --webroot-path=/var/www/html -d nexus-repo.domain.world

[/simterm]

Обновляем NGINX – добавляем в /etc/nginx/conf.d/nexus-repo.domain.world.conf server {} для SSL:

upstream nexus {
    server 127.0.0.1:8081;
}

server {

        listen 80;
        server_name nexus-repo.domain.world;

        # Lets Encrypt Webroot
        location ~ /.well-known {
            root /var/www/html;
            allow all;
        }

        location / {
            return 301 https://nexus-repo.domain.world$request_uri;
        }
}

server {
        
        listen 443 ssl;
        
        server_name nexus-repo.domain.world;
        
        root /var/www/html;
        
        error_log /var/log/nginx/nexus-repo.domain.world-error.log;
        access_log /var/log/nginx/nexus-repo.domain.world-access.log;
        
        ssl on;
        ssl_certificate /etc/letsencrypt/live/nexus-repo.domain.world/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/nexus-repo.domain.world/privkey.pem;
        
        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto https;
            proxy_pass       http://nexus;
        }
}

Проверяем конфиги, перезапускаем:

[simterm]

root@ip-172-31-36-107:/etc/nginx# nginx -t && service nginx reload
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

[/simterm]

И проверяем работу Nexus:

В целом на этом всё.

Уже когда буду делать автоматизацию – EBS будет бекапиться по расписанию, плюс у самого Nexus есть возможность бекепа-восстановления.