Sonatype Nexus๋ ๊ฐ๋ฐ์๊ฐ Java(Maven) ์ข ์์ฑ, Docker, Python, Ruby, NPM, Bower ์ด๋ฏธ์ง, RPM ํจํค์ง, gitlfs, Apt, Go, Nuget์ ํ๋ก์, ์ ์ฅ ๋ฐ ๊ด๋ฆฌํ๊ณ ์ํํธ์จ์ด ๋ณด์์ ๋ฐฐํฌํ ์ ์๋ ํตํฉ ํ๋ซํผ์ ๋๋ค.
Sonatype Nexus๊ฐ ํ์ํ ์ด์ ๋ ๋ฌด์์ ๋๊น?
- ๊ฐ์ธ ์ ๋ฌผ์ ์ ์ฅํ๊ธฐ ์ํด
- ์ธํฐ๋ท์์ ๋ค์ด๋ก๋ํ ์ํฐํฉํธ๋ฅผ ์บ์ฑํ๋ ๊ฒฝ์ฐ
๊ธฐ๋ณธ Sonatype Nexus ํจํค์ง์์ ์ง์๋๋ ์ํฐํฉํธ:
- ์๋ฐ, ๋ฉ์ด๋ธ(jar)
- ๋์ปค
- ํ์ด์ฌ(ํ)
- ๋ฃจ๋น(๋ณด์)
- NPM
- ๋๋ฌด ๊ทธ๋
- ๋ (rpm)
- gitlfs
- ์ด๊ฐ์ด ๋ฒ์ด ์ง
- ์ํํธ(deb)
- Go
- ๋๊ฒ
์ปค๋ฎค๋ํฐ ์ง์ ์ ๋ฌผ:
- ์๊ณก๊ฐ
- ์ฝ๋
- CPAN
- ์ํ
- ํค
- P2
- R
๋ค์์ ์ฌ์ฉํ์ฌ Sonatype Nexus ์ค์น https://github.com/ansible-ThoTeam/nexus3-oss
์๊ตฌ ์ฌํญ
- ์ธํฐ๋ท์์ ansible ์ฌ์ฉ์ ๋ํด ์ฝ์ด๋ณด์ธ์.
- ์ค์๋ธ ์ค์น
pip install ansible
ํ๋ ์ด๋ถ์ด ์คํ๋๋ ์ํฌ์คํ ์ด์ ์์. - ์ธํธ
geerlingguy.java ํ๋ ์ด๋ถ์ด ์คํ๋๋ ์ํฌ์คํ ์ด์ ์์. - ์ธํธ
geerlingguy.apache ํ๋ ์ด๋ถ์ด ์คํ๋๋ ์ํฌ์คํ ์ด์ ์์. - ์ด ์ญํ ์ CentOS 7, Ubuntu Xenial(16.04) ๋ฐ Bionic(18.04), Debian Jessie ๋ฐ Stretch์์ ํ ์คํธ๋์์ต๋๋ค.
jmespath
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํ๋ ์ด๋ถ์ด ์คํ๋๋ ์ํฌ์คํ ์ด์ ์ ์ค์น๋์ด์ผ ํฉ๋๋ค. ์ค์นํ๊ธฐ ์ํด์:sudo pip install -r requirements.txt
- ํ๋ ์ด๋ถ ํ์ผ(์๋ ์)์ nexus.yml ํ์ผ์ ์ ์ฅํฉ๋๋ค.
- ๋ฅ์์ค ์ค์น ์คํ
ansible-playbook -i host nexus.yml
Maven(java), Docker, Python, Ruby, NPM, Bower, RPM ๋ฐ gitlfs ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ LDAP ์์ด nexus๋ฅผ ์ค์นํ๊ธฐ ์ํ ์์ ansible-playbook์ ๋๋ค.
---
- name: Nexus
hosts: nexus
become: yes
vars:
nexus_timezone: 'Asia/Omsk'
nexus_admin_password: "admin123"
nexus_public_hostname: 'apatsev-nexus-playbook'
httpd_setup_enable: false
nexus_privileges:
- name: all-repos-read
description: 'Read & Browse access to all repos'
repository: '*'
actions:
- read
- browse
- name: company-project-deploy
description: 'Deployments to company-project'
repository: company-project
actions:
- add
- edit
nexus_roles:
- id: Developpers # maps to the LDAP group
name: developers
description: All developers
privileges:
- nx-search-read
- all-repos-read
- company-project-deploy
roles: []
nexus_local_users:
- username: jenkins # used as key to update
first_name: Jenkins
last_name: CI
email: [email protected]
password: "s3cr3t"
roles:
- Developpers # role ID here
nexus_blobstores:
- name: company-artifacts
path: /var/nexus/blobs/company-artifacts
nexus_scheduled_tasks:
- name: compact-blobstore
cron: '0 0 22 * * ?'
typeId: blobstore.compact
taskProperties:
blobstoreName: 'company-artifacts'
nexus_repos_maven_proxy:
- name: central
remote_url: 'https://repo1.maven.org/maven2/'
layout_policy: permissive
- name: jboss
remote_url: 'https://repository.jboss.org/nexus/content/groups/public-jboss/'
- name: vaadin-addons
remote_url: 'https://maven.vaadin.com/vaadin-addons/'
- name: jaspersoft
remote_url: 'https://jaspersoft.artifactoryonline.com/jaspersoft/jaspersoft-repo/'
version_policy: mixed
nexus_repos_maven_hosted:
- name: company-project
version_policy: mixed
write_policy: allow
blob_store: company-artifacts
nexus_repos_maven_group:
- name: public
member_repos:
- central
- jboss
- vaadin-addons
- jaspersoft
# Yum. Change nexus_config_yum to true for create yum repository
nexus_config_yum: true
nexus_repos_yum_hosted:
- name: private_yum_centos_7
repodata_depth: 1
nexus_repos_yum_proxy:
- name: epel_centos_7_x86_64
remote_url: http://download.fedoraproject.org/pub/epel/7/x86_64
maximum_component_age: -1
maximum_metadata_age: -1
negative_cache_ttl: 60
- name: centos-7-os-x86_64
remote_url: http://mirror.centos.org/centos/7/os/x86_64/
maximum_component_age: -1
maximum_metadata_age: -1
negative_cache_ttl: 60
nexus_repos_yum_group:
- name: yum_all
member_repos:
- private_yum_centos_7
- epel_centos_7_x86_64
# NPM. Change nexus_config_npm to true for create npm repository
nexus_config_npm: true
nexus_repos_npm_hosted: []
nexus_repos_npm_group:
- name: npm-public
member_repos:
- npm-registry
nexus_repos_npm_proxy:
- name: npm-registry
remote_url: https://registry.npmjs.org/
negative_cache_enabled: false
# Docker. Change nexus_config_docker to true for create docker repository
nexus_config_docker: true
nexus_repos_docker_hosted:
- name: docker-hosted
http_port: "{{ nexus_docker_hosted_port }}"
v1_enabled: True
nexus_repos_docker_proxy:
- name: docker-proxy
http_port: "{{ nexus_docker_proxy_port }}"
v1_enabled: True
index_type: "HUB"
remote_url: "https://registry-1.docker.io"
use_nexus_certificates_to_access_index: false
maximum_component_age: 1440
maximum_metadata_age: 1440
negative_cache_enabled: true
negative_cache_ttl: 1440
nexus_repos_docker_group:
- name: docker-group
http_port: "{{ nexus_docker_group_port }}"
v1_enabled: True
member_repos:
- docker-hosted
- docker-proxy
# Bower. Change nexus_config_bower to true for create bower repository
nexus_config_bower: true
nexus_repos_bower_hosted:
- name: bower-hosted
nexus_repos_bower_proxy:
- name: bower-proxy
index_type: "proxy"
remote_url: "https://registry.bower.io"
use_nexus_certificates_to_access_index: false
maximum_component_age: 1440
maximum_metadata_age: 1440
negative_cache_enabled: true
negative_cache_ttl: 1440
nexus_repos_bower_group:
- name: bower-group
member_repos:
- bower-hosted
- bower-proxy
# Pypi. Change nexus_config_pypi to true for create pypi repository
nexus_config_pypi: true
nexus_repos_pypi_hosted:
- name: pypi-hosted
nexus_repos_pypi_proxy:
- name: pypi-proxy
index_type: "proxy"
remote_url: "https://pypi.org/"
use_nexus_certificates_to_access_index: false
maximum_component_age: 1440
maximum_metadata_age: 1440
negative_cache_enabled: true
negative_cache_ttl: 1440
nexus_repos_pypi_group:
- name: pypi-group
member_repos:
- pypi-hosted
- pypi-proxy
# rubygems. Change nexus_config_rubygems to true for create rubygems repository
nexus_config_rubygems: true
nexus_repos_rubygems_hosted:
- name: rubygems-hosted
nexus_repos_rubygems_proxy:
- name: rubygems-proxy
index_type: "proxy"
remote_url: "https://rubygems.org"
use_nexus_certificates_to_access_index: false
maximum_component_age: 1440
maximum_metadata_age: 1440
negative_cache_enabled: true
negative_cache_ttl: 1440
nexus_repos_rubygems_group:
- name: rubygems-group
member_repos:
- rubygems-hosted
- rubygems-proxy
# gitlfs. Change nexus_config_gitlfs to true for create gitlfs repository
nexus_config_gitlfs: true
nexus_repos_gitlfs_hosted:
- name: gitlfs-hosted
roles:
- { role: geerlingguy.java }
# Debian/Ubuntu only
# - { role: geerlingguy.apache, apache_create_vhosts: no, apache_mods_enabled: ["proxy_http.load", "headers.load"], apache_remove_default_vhost: true, tags: ["geerlingguy.apache"] }
# RedHat/CentOS only
- { role: geerlingguy.apache, apache_create_vhosts: no, apache_remove_default_vhost: true, tags: ["geerlingguy.apache"] }
- { role: ansible-thoteam.nexus3-oss, tags: ['ansible-thoteam.nexus3-oss'] }
์คํฌ๋ฆฐ์ท:
๋ค์ํ ์ญํ
์ญํ ๋ณ์
๊ธฐ๋ณธ๊ฐ์ด ์๋ ๋ณ์(์ฐธ์กฐ default/main.yml
):
์ผ๋ฐ ๋ณ์
nexus_version: ''
nexus_timezone: 'UTC'
๊ธฐ๋ณธ์ ์ผ๋ก ํด๋น ์ญํ ์ ์ฌ์ฉ ๊ฐ๋ฅํ ์ต์ ๋ฒ์ ์ Nexus๋ฅผ ์ค์นํฉ๋๋ค. ๋ณ์๋ฅผ ๋ณ๊ฒฝํ์ฌ ๋ฒ์ ์ ์์ ํ ์ ์์ต๋๋ค nexus_version
. ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฒ์ ์ ํ์ธํ์ธ์.
์ต์ ๋ฒ์ ์ผ๋ก ๋ณ๊ฒฝํ๋ฉด ์ญํ ์ด Nexus ์ค์น ์ ๋ฐ์ดํธ๋ฅผ ์๋ํฉ๋๋ค.
์ต์ ๋ฒ์ ๋ณด๋ค ์ด์ ๋ฒ์ ์ Nexus๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ค์น๋ ๋ฆด๋ฆฌ์ค์์ ์ฌ์ฉํ ์ ์๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ณ ์์ง ์์์ง ํ์ธํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด yum ์ ์ฅ์ ํธ์คํ ์ 3.8.0๋ณด๋ค ํฐ nexus, git lfs repo์์ ์ฌ์ฉํ ์ ์์ต๋๋ค. 3.3.0๋ณด๋ค ํฐ ๋ฅ์์ค์ ๊ฒฝ์ฐ ๋ฑ)
nexus timezone
nexus_scheduled ์์
์ ๋ํด ๋ค์ cron ํํ์๊ณผ ํจ๊ป ์ฌ์ฉํ๋ฉด ์ ์ฉํ ์ ์๋ Java ์๊ฐ๋์ ์ด๋ฆ์
๋๋ค.
Nexus ํฌํธ ๋ฐ ์ปจํ ์คํธ ๊ฒฝ๋ก
nexus_default_port: 8081
nexus_default_context_path: '/'
Java ์ฐ๊ฒฐ ํ๋ก์ธ์ค์ ํฌํธ ๋ฐ ์ปจํ
์คํธ ๊ฒฝ๋ก์
๋๋ค. nexus_default_context_path
์ค์ ์ ์ฌ๋์๋ฅผ ํฌํจํด์ผ ํฉ๋๋ค. ์: nexus_default_context_path: '/nexus/'
.
Nexus OS ์ฌ์ฉ์ ๋ฐ ๊ทธ๋ฃน
nexus_os_group: 'nexus'
nexus_os_user: 'nexus'
Nexus ํ์ผ์ ์์ ํ๊ณ ์๋น์ค๋ฅผ ์คํํ๋ ๋ฐ ์ฌ์ฉ๋๋ ์ฌ์ฉ์ ๋ฐ ๊ทธ๋ฃน์ด ๋๋ฝ๋ ๊ฒฝ์ฐ ํด๋น ์ญํ ์ ์ํด ์์ฑ๋ฉ๋๋ค.
nexus_os_user_home_dir: '/home/nexus'
Nexus ์ฌ์ฉ์์ ๊ธฐ๋ณธ ํ ๋๋ ํฐ๋ฆฌ ๋ณ๊ฒฝ์ ํ์ฉํฉ๋๋ค.
Nexus ์ธ์คํด์ค ๋๋ ํฐ๋ฆฌ
nexus_installation_dir: '/opt'
nexus_data_dir: '/var/nexus'
nexus_tmp_dir: "{{ (ansible_os_family == 'RedHat') | ternary('/var/nexus-tmp', '/tmp/nexus') }}"
๋ฅ์์ค ์นดํ๋ก๊ทธ.
nexus_installation_dir
์ค์น๋ ์คํ ํ์ผ์ด ํฌํจ๋์ด ์์ต๋๋ค.nexus_data_dir
๋ชจ๋ ๊ตฌ์ฑ, ์ ์ฅ์ ๋ฐ ๋ค์ด๋ก๋๋ ์ํฐํฉํธ๊ฐ ํฌํจ๋์ด ์์ต๋๋ค. ์ฌ์ฉ์ ์ ์ Blobstore ๊ฒฝ๋กnexus_data_dir
์ฌ์ฉ์ ์ ์ํ ์ ์์ต๋๋ค. ์๋๋ฅผ ์ฐธ์กฐํ์ธ์.nexus_blobstores
.nexus_tmp_dir
๋ชจ๋ ์์ ํ์ผ์ ํฌํจํฉ๋๋ค. Redhat์ ๊ธฐ๋ณธ ๊ฒฝ๋ก๊ฐ ๋ค์์์ ์ด๋๋์์ต๋๋ค./tmp
์๋ ์ฒญ์ ์ ์ฐจ์ ์ ์ฌ์ ์ธ ๋ฌธ์ ๋ฅผ ๊ทน๋ณตํฉ๋๋ค. #168์ ์ฐธ์กฐํ์ธ์.
Nexus JVM ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๊ตฌ์ฑ
nexus_min_heap_size: "1200M"
nexus_max_heap_size: "{{ nexus_min_heap_size }}"
nexus_max_direct_memory: "2G"
Nexus์ ๊ธฐ๋ณธ ์ค์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค. ์ด ๊ฐ์ ๋ณ๊ฒฝํ์ง ๋ง์ญ์์ค. ์์ง ์ฝ์ง ์์๋ค๋ฉด
๋ ๋ฒ์งธ ๊ฒฝ๊ณ ๋ก, ์ ๋ฌธ์์์ ๋ฐ์ทํ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์ฑ๋ฅ ํฅ์์ ์ํด JVM ํ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ถ์ฅ ๊ฐ ์ด์์ผ๋ก ๋๋ฆฌ๋ ๊ฒ์ ๊ถ์ฅ๋์ง ์์ต๋๋ค. ์ด๋ ์ค์ ๋ก ๋ฐ๋ ํจ๊ณผ๋ฅผ ๊ฐ์ ธ์ ์ด์ ์ฒด์ ์ ๋ถํ์ํ ์์ ์ ์ด๋ํ ์ ์์ต๋๋ค.
๊ด๋ฆฌ์ ๋น๋ฐ๋ฒํธ
nexus_admin_password: 'changeme'
์ค์ ์ ์ํ โadminโ ๊ณ์ ๋น๋ฐ๋ฒํธ์ ๋๋ค. ์ด๋ ์ฒซ ๋ฒ์งธ ๊ธฐ๋ณธ ์ค์น์์๋ง ์๋ํฉ๋๋ค.. ๋์ค์ ์ญํ ์ ์ฌ์ฉํ์ฌ ๊ด๋ฆฌ์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ณ๊ฒฝํ๋ ค๋ฉด [์ฒซ ์ค์น ํ ๊ด๋ฆฌ์ ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ](#change-admin-password-after-first-install)์ ์ฐธ์กฐํ์ธ์.
ํ๋ ์ด๋ถ์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ผ๋ฐ ํ
์คํธ๋ก ์ ์ฅํ์ง ๋ง๊ณ [ansible-vault ์ํธํ]๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค(
๊ธฐ๋ณธ์ ์ผ๋ก ์ต๋ช ์ก์ธ์ค
nexus_anonymous_access: false
์ต๋ช
์ก์ธ์ค๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋นํ์ฑํ๋์ด ์์ต๋๋ค. ์์ธํ ์์๋ณด๊ธฐ
๊ณต๊ฐ ํธ์คํธ ์ด๋ฆ
nexus_public_hostname: 'nexus.vm'
nexus_public_scheme: https
ํด๋ผ์ด์ธํธ๊ฐ Nexus ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํ ์ ์๋ ์ ๊ทํ๋ ๋๋ฉ์ธ ์ด๋ฆ ๋ฐ ์ฒด๊ณ(https ๋๋ http)์ ๋๋ค.
์ด ์ญํ ์ ๋ํ API ์ก์ธ์ค
nexus_api_hostname: localhost
nexus_api_scheme: http
nexus_api_validate_certs: "{{ nexus_api_scheme == 'https' }}"
nexus_api_context_path: "{{ nexus_default_context_path }}"
nexus_api_port: "{{ nexus_default_port }}"
์ด๋ฌํ ๋ณ์๋ ํ๋ก๋น์ ๋์ ์ํด ์ญํ ์ด Nexus API์ ์ฐ๊ฒฐ๋๋ ๋ฐฉ์์ ์ ์ดํฉ๋๋ค.
๊ณ ๊ธ ์ฌ์ฉ์์๊ฒ๋ง ํด๋น๋ฉ๋๋ค. ์๋ง๋ ์ด๋ฌํ ๊ธฐ๋ณธ ์ค์ ์ ๋ณ๊ฒฝํ๊ณ ์ถ์ง ์์ ๊ฒ์
๋๋ค.
์ญ๋ฐฉํฅ ํ๋ก์ ์ค์
httpd_setup_enable: false
httpd_server_name: "{{ nexus_public_hostname }}"
httpd_default_admin_email: "[email protected]"
httpd_ssl_certificate_file: 'files/nexus.vm.crt'
httpd_ssl_certificate_key_file: 'files/nexus.vm.key'
# httpd_ssl_certificate_chain_file: "{{ httpd_ssl_certificate_file }}"
httpd_copy_ssl_files: true
์ธํธ
์ด๋ฅผ ์ํด์๋ httpd๋ฅผ ์ค์นํด์ผ ํฉ๋๋ค. ์ฐธ๊ณ : ์ธ์ httpd_setup_enable
์ค์ ๊ฐtrue
, ๋ฅ์์ค๋ 127.0.0.1:8081์ ์ ์ํ๋ฏ๋ก ์๋ ์ธ๋ถ IP ์ฃผ์์์ HTTP ํฌํธ 8081์ ํตํด ์ง์ ์ก์ธ์คํ ์ ์์ต๋๋ค.
์ฌ์ฉ๋๋ ๊ธฐ๋ณธ ํธ์คํธ ์ด๋ฆ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค. nexus_public_hostname
. ์ด๋ค ์ด์ ๋ก ๋ค๋ฅธ ์ด๋ฆ์ด ํ์ํ ๊ฒฝ์ฐ ๋ค์์ ์ค์ ํ ์ ์์ต๋๋ค. httpd_server_name
๋ค๋ฅธ ์๋ฏธ๋ก.
ะก httpd_copy_ssl_files: true
(๊ธฐ๋ณธ์ ์ผ๋ก) ์ ์ธ์ฆ์๋ ํ๋ ์ด๋ถ ๋๋ ํฐ๋ฆฌ์ ์์ด์ผ ํ๋ฉฐ ์๋ฒ์ ๋ณต์ฌ๋๊ณ Apache์์ ๊ตฌ์ฑ๋ฉ๋๋ค.
์๋ฒ์ ์๋ ๊ธฐ์กด ์ธ์ฆ์๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด httpd_copy_ssl_files: false
๋ค์ ๋ณ์๋ฅผ ์ ๊ณตํฉ๋๋ค.
# These specifies to the vhost where to find on the remote server file
# system the certificate files.
httpd_ssl_cert_file_location: "/etc/pki/tls/certs/wildcard.vm.crt"
httpd_ssl_cert_key_location: "/etc/pki/tls/private/wildcard.vm.key"
# httpd_ssl_cert_chain_file_location: "{{ httpd_ssl_cert_file_location }}"
httpd_ssl_cert_chain_file_location
์ ํ ์ฌํญ์ด๋ฉฐ ์ฒด์ธ ํ์ผ์ ์ฌ์ฉ์ ์ ์ํ์ง ์์ผ๋ ค๋ฉด ์ค์ ํ์ง ์์ ์ํ๋ก ๋์ด์ผ ํฉ๋๋ค.
httpd_default_admin_email: "[email protected]"
๊ธฐ๋ณธ ๊ด๋ฆฌ์ ์ด๋ฉ์ผ ์ฃผ์ ์ค์
LDAP ๊ตฌ์ฑ
LDAP ์ฐ๊ฒฐ ๋ฐ ๋ณด์ ์์ญ์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋นํ์ฑํ๋์ด ์์ต๋๋ค.
nexus_ldap_realm: false
ldap_connections: []
nexus_ldap_realm: true
ldap_connections:
- ldap_name: 'My Company LDAP' # used as a key to update the ldap config
ldap_protocol: 'ldaps' # ldap or ldaps
ldap_hostname: 'ldap.mycompany.com'
ldap_port: 636
ldap_use_trust_store: false # Wether or not to use certs in the nexus trust store
ldap_search_base: 'dc=mycompany,dc=net'
ldap_auth: 'none' # or simple
ldap_auth_username: 'username' # if auth = simple
ldap_auth_password: 'password' # if auth = simple
ldap_user_base_dn: 'ou=users'
ldap_user_filter: '(cn=*)' # (optional)
ldap_user_object_class: 'inetOrgPerson'
ldap_user_id_attribute: 'uid'
ldap_user_real_name_attribute: 'cn'
ldap_user_email_attribute: 'mail'
ldap_user_subtree: false
ldap_map_groups_as_roles: false
ldap_group_base_dn: 'ou=groups'
ldap_group_object_class: 'posixGroup'
ldap_group_id_attribute: 'cn'
ldap_group_member_attribute: 'memberUid'
ldap_group_member_format: '${username}'
ldap_group_subtree: false
์ต๋ช ์ธ์ฆ(์ต๋ช ๋ฐ์ธ๋ฉ)์ ์ํ LDAP ๊ตฌ์ฑ ์๋ "์ต์" ๊ตฌ์ฑ์ด๊ธฐ๋ ํฉ๋๋ค.
nexus_ldap_realm: true
ldap_connection:
- ldap_name: 'Simplest LDAP config'
ldap_protocol: 'ldaps'
ldap_hostname: 'annuaire.mycompany.com'
ldap_search_base: 'dc=mycompany,dc=net'
ldap_port: 636
ldap_use_trust_store: false
ldap_user_id_attribute: 'uid'
ldap_user_real_name_attribute: 'cn'
ldap_user_email_attribute: 'mail'
ldap_user_object_class: 'inetOrgPerson'
๋จ์ ์ธ์ฆ์ ์ํ LDAP ๊ตฌ์ฑ ์(DSA ๊ณ์ ์ฌ์ฉ):
nexus_ldap_realm: true
ldap_connections:
- ldap_name: 'LDAP config with DSA'
ldap_protocol: 'ldaps'
ldap_hostname: 'annuaire.mycompany.com'
ldap_port: 636
ldap_use_trust_store: false
ldap_auth: 'simple'
ldap_auth_username: 'cn=mynexus,ou=dsa,dc=mycompany,dc=net'
ldap_auth_password: "{{ vault_ldap_dsa_password }}" # better keep passwords in an ansible vault
ldap_search_base: 'dc=mycompany,dc=net'
ldap_user_base_dn: 'ou=users'
ldap_user_object_class: 'inetOrgPerson'
ldap_user_id_attribute: 'uid'
ldap_user_real_name_attribute: 'cn'
ldap_user_email_attribute: 'mail'
ldap_user_subtree: false
๋จ์ ์ธ์ฆ(DSA ๊ณ์ ์ฌ์ฉ)์ ์ํ LDAP ๊ตฌ์ฑ ์์ + ์ญํ ๋ก ๋งคํ๋ ๊ทธ๋ฃน:
nexus_ldap_realm: true
ldap_connections
- ldap_name: 'LDAP config with DSA'
ldap_protocol: 'ldaps'
ldap_hostname: 'annuaire.mycompany.com'
ldap_port: 636
ldap_use_trust_store: false
ldap_auth: 'simple'
ldap_auth_username: 'cn=mynexus,ou=dsa,dc=mycompany,dc=net'
ldap_auth_password: "{{ vault_ldap_dsa_password }}" # better keep passwords in an ansible vault
ldap_search_base: 'dc=mycompany,dc=net'
ldap_user_base_dn: 'ou=users'
ldap_user_object_class: 'inetOrgPerson'
ldap_user_id_attribute: 'uid'
ldap_user_real_name_attribute: 'cn'
ldap_user_email_attribute: 'mail'
ldap_map_groups_as_roles: true
ldap_group_base_dn: 'ou=groups'
ldap_group_object_class: 'groupOfNames'
ldap_group_id_attribute: 'cn'
ldap_group_member_attribute: 'member'
ldap_group_member_format: 'uid=${username},ou=users,dc=mycompany,dc=net'
ldap_group_subtree: false
๋จ์ ์ธ์ฆ(DSA ๊ณ์ ์ฌ์ฉ)์ ์ํ LDAP ๊ตฌ์ฑ ์ + ์ญํ ๋ก ๋์ ์ผ๋ก ๋งคํ๋ ๊ทธ๋ฃน:
nexus_ldap_realm: true
ldap_connections:
- ldap_name: 'LDAP config with DSA'
ldap_protocol: 'ldaps'
ldap_hostname: 'annuaire.mycompany.com'
ldap_port: 636
ldap_use_trust_store: false
ldap_auth: 'simple'
ldap_auth_username: 'cn=mynexus,ou=dsa,dc=mycompany,dc=net'
ldap_auth_password: "{{ vault_ldap_dsa_password }}" # better keep passwords in an ansible vault
ldap_search_base: 'dc=mycompany,dc=net'
ldap_user_base_dn: 'ou=users'
ldap_user_object_class: 'inetOrgPerson'
ldap_user_id_attribute: 'uid'
ldap_user_real_name_attribute: 'cn'
ldap_user_email_attribute: 'mail'
ldap_map_groups_as_roles: true
ldap_map_groups_as_roles_type: 'dynamic'
ldap_user_memberof_attribute: 'memberOf'
ํน๊ถ
nexus_privileges:
- name: all-repos-read # used as key to update a privilege
# type: <one of application, repository-admin, repository-content-selector, repository-view, script or wildcard>
description: 'Read & Browse access to all repos'
repository: '*'
actions: # can be add, browse, create, delete, edit, read or * (all)
- read
- browse
# pattern: pattern
# domain: domain
# script_name: name
๋ช
๋ถ
์ด๋ฌํ ์์๋ ๋ค์ ๊ธฐ๋ณธ๊ฐ๊ณผ ๊ฒฐํฉ๋ฉ๋๋ค.
_nexus_privilege_defaults:
type: repository-view
format: maven2
actions:
- read
์ญํ (๋ด๋ถ Nexus ์๋ฏธ)
nexus_roles:
- id: Developpers # can map to a LDAP group id, also used as a key to update a role
name: developers
description: All developers
privileges:
- nx-search-read
- all-repos-read
roles: [] # references to other role names
๋ช
๋ถ
ํ์
nexus_local_users: []
# - username: jenkins # used as key to update
# state: present # default value if ommited, use 'absent' to remove user
# first_name: Jenkins
# last_name: CI
# email: [email protected]
# password: "s3cr3t"
# roles:
# - developers # role ID
๋ฅ์์ค์์ ์์ฑํ ๋ก์ปฌ(๋นLDAP) ์ฌ์ฉ์/๊ณ์ ๋ชฉ๋ก์ ๋๋ค.
Nexus์์ ์์ฑํ ๋ก์ปฌ(๋นLDAP) ์ฌ์ฉ์/๊ณ์ ๋ชฉ๋ก์ ๋๋ค.
nexus_ldap_users: []
# - username: j.doe
# state: present
# roles:
# - "nx-admin"
์ฌ์ฉ์/์ญํ ์ LDAP ๋งคํ. ์ํ absent
๊ธฐ์กด ์ฌ์ฉ์์ ์ญํ ์ด ์ด๋ฏธ ์กด์ฌํ๋ ๊ฒฝ์ฐ ํด๋น ์ญํ ์ ์ ๊ฑฐํฉ๋๋ค.
LDAP ์ฌ์ฉ์๋ ์ญ์ ๋์ง ์์ต๋๋ค. ์กด์ฌํ์ง ์๋ ์ฌ์ฉ์์ ๋ํ ์ญํ ์ ์ค์ ํ๋ ค๊ณ ํ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
์ฝํ ์ธ ์ ํ๊ธฐ
nexus_content_selectors:
- name: docker-login
description: Selector for docker login privilege
search_expression: format=="docker" and path=~"/v2/"
์ฝํ
์ธ ์ ํ๊ธฐ์ ๋ํ ์์ธํ ๋ด์ฉ์ ๋ค์์ ์ฐธ์กฐํ์ธ์.
์ฝํ
์ธ ์ ํ๊ธฐ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋ค์์ ์ฌ์ฉํ์ฌ ์ ๊ถํ์ ์ถ๊ฐํ์ธ์. type: repository-content-selector
๊ด๋ จ์ฑ์ด ์๊ณ contentSelector
- name: docker-login-privilege
type: repository-content-selector
contentSelector: docker-login
description: 'Login to Docker registry'
repository: '*'
actions:
- read
- browse
Blobstore ๋ฐ ์ ์ฅ์
nexus_delete_default_repos: false
Nexus ์ค์น ์ด๊ธฐ ๊ธฐ๋ณธ ๊ตฌ์ฑ์์ ์ ์ฅ์๋ฅผ ์ญ์ ํฉ๋๋ค. ์ด ๋จ๊ณ๋ ์ต์ด ์ค์น ์์๋ง ์คํ๋ฉ๋๋ค. nexus_data_dir
๋น์ด ์๋ ๊ฒ์ผ๋ก ๊ฐ์ง๋์์ต๋๋ค).
Nexus์ ๊ธฐ๋ณธ ๊ธฐ๋ณธ ๊ตฌ์ฑ์์ ์ ์ฅ์๋ฅผ ์ ๊ฑฐํฉ๋๋ค. ์ด ๋จ๊ณ๋ ์ฒ์ ์ค์นํ๋ ๋์์๋ง ์ํ๋ฉ๋๋ค( nexus_data_dir
๋น์ด ์๋).
nexus_delete_default_blobstore: false
nexus ์ค์น ์ด๊ธฐ ๊ธฐ๋ณธ ๊ตฌ์ฑ์์ ๊ธฐ๋ณธ blobstore๋ฅผ ์ญ์ ํฉ๋๋ค. ์ด ์์
์ ๋ค์ ๊ฒฝ์ฐ์๋ง ์ํํ ์ ์์ต๋๋ค. nexus_delete_default_repos: true
๊ตฌ์ฑ๋ ๋ชจ๋ ์ ์ฅ์(์๋ ์ฐธ์กฐ)์๋ ๋ช
์์ ์ธ blob_store: custom
. ์ด ๋จ๊ณ๋ ์ต์ด ์ค์น ์์๋ง ์คํ๋ฉ๋๋ค. nexus_data_dir
๋น์ด ์๋ ๊ฒ์ผ๋ก ๊ฐ์ง๋์์ต๋๋ค).
Blob Storage(์ด์ง ์ํฐํฉํธ) ์ ๊ฑฐ๋ ์ด๊ธฐ ๊ตฌ์ฑ์์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋นํ์ฑํ๋ฉ๋๋ค. Blob Storage(๋ฐ์ด๋๋ฆฌ ์ํฐํฉํธ)๋ฅผ ์ ๊ฑฐํ๋ ค๋ฉด ๋์ธ์. nexus_delete_default_repos: true
. ์ด ๋จ๊ณ๋ ์ฒ์ ์ค์นํ๋ ๋์์๋ง ์ํ๋ฉ๋๋ค( nexus_data_dir
๋น์ด ์๋).
nexus_blobstores: []
# example blobstore item :
# - name: separate-storage
# type: file
# path: /mnt/custom/path
# - name: s3-blobstore
# type: S3
# config:
# bucket: s3-blobstore
# accessKeyId: "{{ VAULT_ENCRYPTED_KEY_ID }}"
# secretAccessKey: "{{ VAULT_ENCRYPTED_ACCESS_KEY }}"
S3์์ blobstore๋ฅผ ๊ตฌ์ฑํ๋ ๊ฒ์ ํธ์๋ฅผ ์ํด ์ ๊ณต๋๋ฉฐ travis์์ ์คํํ๋ ์๋ํ๋ ํ ์คํธ์ ์ผ๋ถ๊ฐ ์๋๋๋ค. S3์ ์ ์ฅํ๋ ๊ฒ์ AWS์ ๋ฐฐํฌ๋ ์ธ์คํด์ค์๋ง ๊ถ์ฅ๋ฉ๋๋ค.
์ฐฝ์กฐ
S3์์ Blob Storage ์ค์ ์ ํธ์๋ฅผ ์ํด ์ ๊ณต๋ฉ๋๋ค. S3 ์คํ ๋ฆฌ์ง๋ AWS์ ๋ฐฐํฌ๋ ์ธ์คํด์ค์๋ง ๊ถ์ฅ๋ฉ๋๋ค.
nexus_repos_maven_proxy:
- name: central
remote_url: 'https://repo1.maven.org/maven2/'
layout_policy: permissive
# maximum_component_age: -1
# maximum_metadata_age: 1440
# negative_cache_enabled: true
# negative_cache_ttl: 1440
- name: jboss
remote_url: 'https://repository.jboss.org/nexus/content/groups/public-jboss/'
# maximum_component_age: -1
# maximum_metadata_age: 1440
# negative_cache_enabled: true
# negative_cache_ttl: 1440
# example with a login/password :
# - name: secret-remote-repo
# remote_url: 'https://company.com/repo/secure/private/go/away'
# remote_username: 'username'
# remote_password: 'secret'
# # maximum_component_age: -1
# # maximum_metadata_age: 1440
# # negative_cache_enabled: true
# # negative_cache_ttl: 1440
์๋ ๊ตฌ์ฑ ์์
๋๋ค.
nexus_repos_maven_hosted:
- name: private-release
version_policy: release
write_policy: allow_once # one of "allow", "allow_once" or "deny"
๋ฉ์ด๋ธ
๊ตฌ์ฑ
nexus_repos_maven_group:
- name: public
member_repos:
- central
- jboss
๊ตฌ์ฑ
์ธ ๊ฐ์ง ์ ์ฅ์ ์ ํ์ ๋ชจ๋ ๋ค์ ๊ธฐ๋ณธ๊ฐ๊ณผ ๊ฒฐํฉ๋ฉ๋๋ค.
_nexus_repos_maven_defaults:
blob_store: default # Note : cannot be updated once the repo has been created
strict_content_validation: true
version_policy: release # release, snapshot or mixed
layout_policy: strict # strict or permissive
write_policy: allow_once # one of "allow", "allow_once" or "deny"
maximum_component_age: -1 # Nexus gui default. For proxies only
maximum_metadata_age: 1440 # Nexus gui default. For proxies only
negative_cache_enabled: true # Nexus gui default. For proxies only
negative_cache_ttl: 1440 # Nexus gui default. For proxies only
Docker, Pypi, Raw, Rubygems, Bower, NPM, Git-LFS ๋ฐ yum ์ ์ฅ์ ์ ํ:
์ฐธ์กฐ defaults/main.yml
๋ค์ ์ต์
์ ๊ฒฝ์ฐ:
Docker, Pypi, Raw, Rubygems, Bower, NPM, Git-LFS ๋ฐ yum ๋ฆฌํฌ์งํ ๋ฆฌ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋นํ์ฑํ๋์ด ์์ต๋๋ค.
์ฐธ์กฐ defaults/main.yml
๋ค์ ์ต์
์ ๊ฒฝ์ฐ:
nexus_config_pypi: false
nexus_config_docker: false
nexus_config_raw: false
nexus_config_rubygems: false
nexus_config_bower: false
nexus_config_npm: false
nexus_config_gitlfs: false
nexus_config_yum: false
Maven ์ด์ธ์ ๋ค๋ฅธ ์ ํ์ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ํน์ ๋ณด์ ๋ฒ์๋ฅผ ํ์ฑํํด์ผ ํ ์๋ ์์ต๋๋ค. ์ด๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ฑฐ์ง์ ๋๋ค.
nexus_nuget_api_key_realm: false
nexus_npm_bearer_token_realm: false
nexus_docker_bearer_token_realm: false # required for docker anonymous access
์๊ฒฉ ์ฌ์ฉ์ ์์ญ์ ๋ค์์ ์ฌ์ฉํ์ฌ ํ์ฑํํ ์๋ ์์ต๋๋ค.
nexus_rut_auth_realm: true
์ ๋ชฉ์ ๋ค์์ ์ ์ํ์ฌ ๋ง์ถค์ค์ ํ ์ ์์ต๋๋ค.
nexus_rut_auth_header: "CUSTOM_HEADER"
์์ฝ ๋ ์ผ๋ค
nexus_scheduled_tasks: []
# # Example task to compact blobstore :
# - name: compact-docker-blobstore
# cron: '0 0 22 * * ?'
# typeId: blobstore.compact
# task_alert_email: [email protected] # optional
# taskProperties:
# blobstoreName: {{ nexus_blob_names.docker.blob }} # all task attributes are stored as strings by nexus internally
# # Example task to purge maven snapshots
# - name: Purge-maven-snapshots
# cron: '0 50 23 * * ?'
# typeId: repository.maven.remove-snapshots
# task_alert_email: [email protected] # optional
# taskProperties:
# repositoryName: "*" # * for all repos. Change to a repository name if you only want a specific one
# minimumRetained: "2"
# snapshotRetentionDays: "2"
# gracePeriodInDays: "2"
# booleanTaskProperties:
# removeIfReleased: true
# # Example task to purge unused docker manifest and images
# - name: Purge unused docker manifests and images
# cron: '0 55 23 * * ?'
# typeId: "repository.docker.gc"
# task_alert_email: [email protected] # optional
# taskProperties:
# repositoryName: "*" # * for all repos. Change to a repository name if you only want a specific one
# # Example task to purge incomplete docker uploads
# - name: Purge incomplete docker uploads
# cron: '0 0 0 * * ?'
# typeId: "repository.docker.upload-purge"
# task_alert_email: [email protected] # optional
# taskProperties:
# age: "24"
typeId
ํน์ ์์
taskProperties
/booleanTaskProperties
๋ค์ ์ค ํ๋๋ฅผ ์ถ์ธกํ ์ ์์ต๋๋ค.
- Java ์ ํ ๊ณ์ธต ๊ตฌ์กฐ์์
org.sonatype.nexus.scheduling.TaskDescriptorSupport
- ๋ธ๋ผ์ฐ์ ์์ HTML ์์ ์์ฑ ์์ ํ์ธ
- ์์ ์ ์๋์ผ๋ก ์ค์ ํ ๋ ๋ธ๋ผ์ฐ์ ์์ AJAX ์์ฒญ์ ๋ณผ ์ ์์ต๋๋ค.
์์ ์์ฑ์ ํด๋น ์ ํ์ ๋ฐ๋ผ ์ฌ๋ฐ๋ฅธ yaml ๋ธ๋ก์์ ์ ์ธ๋์ด์ผ ํฉ๋๋ค.:
taskProperties
๋ชจ๋ ๋ฌธ์์ด ์์ฑ(์: ์ ์ฅ์ ์ด๋ฆ, ์ ์ฅ์ ์ด๋ฆ, ๊ธฐ๊ฐ...)์ ๋ํด.booleanTaskProperties
๋ชจ๋ ๋ ผ๋ฆฌ์ ์์ฑ(์: ์ฃผ๋ก ๋ฅ์์ค ์์ฑ ์์ GUI์ ํ์ธ๋)์ ๋ํด
๋ฐฑ์
nexus_backup_configure: false
nexus_backup_cron: '0 0 21 * * ?' # See cron expressions definition in nexus create task gui
nexus_backup_dir: '/var/nexus-backup'
nexus_restore_log: '{{ nexus_backup_dir }}/nexus-restore.log'
nexus_backup_rotate: false
nexus_backup_rotate_first: false
nexus_backup_keep_rotations: 4 # Keep 4 backup rotation by default (current + last 3)
์ ํํ ๋๊น์ง ๋ฐฑ์
์ด ๊ตฌ์ฑ๋์ง ์์ต๋๋ค. nexus_backup_configure
ะฒ true
.
์ด ๊ฒฝ์ฐ ์์ฝ๋ ์คํฌ๋ฆฝํธ ์์
์ Nexus์์ ์คํ๋๋๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
์ ์ง์ ๋ ๊ฐ๊ฒฉ์ผ๋ก nexus_backup_cron
(๊ธฐ๋ณธ์ ์ผ๋ก ๋งค์ผ 21:00).
์์ธํ ๋ด์ฉ์ [์ด ์์
์ ์ํ Groovy ํ
ํ๋ฆฟ](templates/backup.groovy.j2)์ ์ฐธ์กฐํ์ธ์.
์ด ์์ฝ๋ ์์
์ ๋ค๋ฅธ ์์
๊ณผ ๋
๋ฆฝ์ ์
๋๋ค. nexus_scheduled_tasks
๋น์ ์ด
ํ๋ ์ด๋ถ์ ๋ฐํํ์ธ์.
๋ฐฑ์
์ ์ํ/์ญ์ ํ๋ ค๋ฉด ๋ค์์ ์ค์นํ์ญ์์ค. nexus_backup_rotate: true
๋ค์์ ์ฌ์ฉํ์ฌ ์ ์ฅํ๋ ค๋ ๋ฐฑ์
์๋ฅผ ๊ตฌ์ฑํฉ๋๋ค. nexus_backup_keep_rotations
(๊ธฐ๋ณธ๊ฐ 4).
์ํ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ๋, ๋ฐฑ์
๊ณผ์ ์์ ์ถ๊ฐ ๋์คํฌ ๊ณต๊ฐ์ ์ ์ฝํ๊ณ ์ถ๋ค๋ฉด,
๋น์ ์ ์ค์นํ ์ ์์ต๋๋ค nexus_backup_rotate_first: true
. ๋ฐฑ์
์ ์ฌ์ ํ์ /์ญ์ ๋ฅผ ๊ตฌ์ฑํฉ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ๊ต์ฒด๋ ๋ฐฑ์
์ด ์์ฑ๋ ํ์ ๋ฐ์ํฉ๋๋ค. ์ด ๊ฒฝ์ฐ ์ด์ ๋ฐฑ์
์
ํ์ฌ ๋ฐฑ์
์ด ์ด๋ฃจ์ด์ง๊ธฐ ์ ์ ์ญ์ ๋ฉ๋๋ค.
๋ณต๊ตฌ ์ ์ฐจ
๋งค๊ฐ๋ณ์๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ ์ด๋ถ ์คํ -e nexus_restore_point=<YYYY-MM-dd-HH-mm-ss>
(์: 2017๋
12์ 17์ผ 21:00์ ๊ฒฝ์ฐ 00-17-2017-21-00-XNUMX)
๋ฅ์์ค ์ญ์ ์ค
๊ฒฝ๊ณ : ํ์ฌ ๋ฐ์ดํฐ๊ฐ ์์ ํ ์ญ์ ๋ฉ๋๋ค. ํ์ํ๋ค๋ฉด ๋ฏธ๋ฆฌ ๋ฐฑ์ ํด๋์ธ์
๋ณ์ ์ฌ์ฉ nexus_purge
์ฒ์๋ถํฐ ๋ค์ ์์ํ๊ณ ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ์ ๊ฑฐ๋ ๋ฅ์์ค ์ธ์คํด์ค๋ฅผ ๋ค์ ์ค์นํด์ผ ํ๋ ๊ฒฝ์ฐ.
ansible-playbook -i your/inventory.ini your_nexus_playbook.yml -e nexus_purge=true
์ต์ด ์ค์น ํ ๊ด๋ฆฌ์ ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ
nexus_default_admin_password: 'admin123'
ํ๋ ์ด๋ถ์์ ๋ณ๊ฒฝํ๋ฉด ์ ๋ฉ๋๋ค.. ์ด ๋ณ์๋ ์ฒ์ ์ค์นํ ๋ ๊ธฐ๋ณธ Nexus ๊ด๋ฆฌ์ ๋น๋ฐ๋ฒํธ๋ก ์ฑ์์ง๋ฉฐ ๊ด๋ฆฌ์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ค์์ผ๋ก ๋ณ๊ฒฝํ ์ ์์ต๋๋ค. nexus_admin_password
.
์ฒ์ ์ค์นํ ํ ๊ด๋ฆฌ์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ณ๊ฒฝํ๋ ค๋ฉด ๋ช
๋ น์ค์์ ์ผ์์ ์ผ๋ก ์ด์ ๋น๋ฐ๋ฒํธ๋ก ๋ณ๊ฒฝํ ์ ์์ต๋๋ค. ๋ณ๊ฒฝ ํ nexus_admin_password
ํ๋ ์ด๋ถ์์ ๋ค์์ ์คํํ ์ ์์ต๋๋ค.
ansible-playbook -i your/inventory.ini your_playbook.yml -e nexus_default_admin_password=oldPassword
Nexus Sonatype์ ํ
๋ ๊ทธ๋จ ์ฑ๋:
๋ฑ๋ก๋ ์ฌ์ฉ์๋ง ์ค๋ฌธ ์กฐ์ฌ์ ์ฐธ์ฌํ ์ ์์ต๋๋ค.
์ด๋ค ์ํฐํฉํธ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ์๋์?
-
์๋ํ์ ๋ฅ์์ค๋ ๋ฌด๋ฃ์ ๋๋ค
-
์๋ํ์ ๋ฅ์์ค ์ ๋ฃ
-
์ํฐํฉํ ๋ฆฌ๋ ๋ฌด๋ฃ์ ๋๋ค
-
์ํฐํฉํ ๋ฆฌ ์ง๊ธ
-
ํญ๊ตฌ
-
ํํ
9๋ช
์ ์ฌ์ฉ์๊ฐ ํฌํํ์ต๋๋ค. 3๋ช
์ ์ฌ์ฉ์๊ฐ ๊ธฐ๊ถํ์ต๋๋ค.
์ถ์ฒ : habr.com