주:
- 이 자습서는 Oracle에서 제공하는 무료 실습 환경에서 사용할 수 있습니다.
- Oracle Cloud Infrastructure 자격 증명, 테넌시 및 구획에 예제 값을 사용합니다. 실습을 완료했으면 이러한 값을 자신의 클라우드 환경과 관련된 값으로 대체하십시오.
Oracle Linux Automation Engine을 사용하여 Oracle Linux 작업 실행
소개
관리자는 Oracle Linux Automation Engine을 통해 일련의 플레이북 및 작업을 통해 코드형 인프라(IaC) 구성 관리 도구를 사용하여 Oracle Linux의 초기 설정을 자동화하고 기타 관리 작업을 실행할 수 있습니다.
목표
이 자습서에서는 다음을 수행하는 방법을 배웁니다.
- 다음과 같은 플레이북을 작성합니다.
- 사용자 생성
- sudo 그룹에 사용자를 추가합니다.
- 로컬 SSH 공개 키를 사용자의 authorized_keys 파일에 복사합니다.
- DNF 저장소를 추가하고 패키지를 설치합니다.
- 파일 조작
필요 조건
-
다음 구성이 포함된 최소 2개의 Oracle Linux 시스템:
- sudo 권한이 있는 비루트 사용자
- 비루트 사용자에 대한 ssh 키 쌍
- 암호 없는 SSH 로그인을 사용하여 호스트 간에 SSH를 지정하는 기능
Oracle Linux Automation Engine 배포
주: 고유 테넌시에서 실행 중인 경우 랩 환경을 배치하기 전에 linux-virt-labs
GitHub 프로젝트 README.md을 읽고 필요 조건을 완료하십시오.
-
Luna Desktop에서 터미널을 엽니다.
-
linux-virt-labs
GitHub 프로젝트를 복제합니다.git clone https://github.com/oracle-devrel/linux-virt-labs.git
-
작업 디렉토리로 변경합니다.
cd linux-virt-labs/olam
-
필요한 모음을 설치합니다.
ansible-galaxy collection install -r requirements.yml
-
Oracle Linux 인스턴스 구성을 업데이트합니다.
cat << EOF | tee instances.yml > /dev/null compute_instances: 1: instance_name: "ol-control-node" type: "control" 2: instance_name: "ol-host" type: "remote" use_olae_only: true EOF
-
lab 환경을 배치합니다.
ansible-playbook create_instance.yml -e localhost_python_interpreter="/usr/bin/python3.6" -e "@instances.yml"
무료 실습 환경에는 추가 변수
local_python_interpreter
이 필요합니다. 이 변수는 localhost에서 실행되는 재생에 대해ansible_python_interpreter
를 설정합니다. 환경이 python3.6 모듈 아래에 있는 Oracle Cloud Infrastructure SDK for Python용 RPM 패키지를 설치하므로 이 변수가 필요합니다.기본 배치 구성은 AMD CPU 및 Oracle Linux 8을 사용합니다. Intel CPU 또는 Oracle Linux 9를 사용하려면 배치 명령에
-e instance_shape="VM.Standard3.Flex"
또는-e os_version="9"
를 추가합니다.중요: 플레이북이 성공적으로 실행될 때까지 기다렸다가 일시 중지 작업에 도달합니다. 이 플레이북 단계에서는 Oracle Linux 설치가 완료되고 인스턴스가 준비됩니다. 배치하는 노드의 공용(public) 및 전용(private) IP 주소와 실습을 실행하는 동안 필요한 기타 배치 정보를 출력하는 이전 플레이를 기록해 둡니다.
초기 설정 플레이북 작성
많은 플레이북은 키-값 쌍을 포함하는 변수와 변수 파일을 활용하므로 코드가 정적으로 유지되는 동안 실제 플레이북 작업을 동적으로 수행할 수 있습니다. 플레이북에는 런타임 중에 해당 값이 작업을 실행할 때 재생의 일부가 되는 변수가 포함되어 있습니다.
Oracle Linux Automation Engine을 사용하면 이러한 변수를 각각 우선 순위를 가지는 여러 위치에서 정의할 수 있습니다. 플레이북 레벨 변수는 vars 또는 vars_files 지시어를 사용하여 플레이북 내에 정의됩니다. vars 지시어는 변수를 재생의 일부로 지정하고, vars_files 지시어는 변수를 포함하는 외부 파일을 포함합니다. 이 예에서는 플레이북을 실행하기 전에 시스템 구성을 정의하는 것처럼 개발자는 이러한 변수를 동적으로 또는 정적으로 다른 플레이에서 만들 수 있습니다.
-
새 터미널을 열고 SSH를 통해 ol-control-node 시스템에 연결합니다.
ssh oracle@<ip_address_of_instance>
-
Oracle Linux Automation Engine 명령을 사용할 수 있는지 확인합니다.
ansible --version
-
작업 프로젝트 디렉토리를 만듭니다.
mkdir -p ~/ol-playbook
-
프로젝트에 대한 변수 디렉토리 및 파일을 생성합니다.
mkdir ~/ol-playbook/vars
touch ~/ol-playbook/vars/defaults.yml
-
변수 및 값을 파일에 추가합니다.
cat << EOF | tee ~/ol-playbook/vars/defaults.yml > /dev/null --- username: oracle user_default_password: oracle ssh_key_file: id_rsa ssh_private_key_file: "{{ lookup('file', lookup('env','HOME') + '/.ssh/' + ssh_key_file + '.pub') }}" additional_packages: ['git'] EOF
이 정보는 각 변수 및 사용 방법에 대해 설명합니다.
username
: 플레이북을 실행할 때 만든 sudo 사용자의 이름입니다. 이 예에서 사용자 이름은oracle
입니다.user_default_password
: 생성할 때oracle
사용자에 대한 기본 비밀번호입니다.sudo
명령을 실행할 때 암호가 필요합니다.ssh_key_file
: 사용자의 SSH 키 쌍의 이름을 설정합니다.ssh_private_key_file
: 사용자의 SSH 퍼블릭 키를 지정된 경로의 원격 사용자의 authorized_key 파일에 복사합니다. 이 예에서는 lookup 플러그인을 사용하여 로컬 사용자$HOME/.ssh/
디렉토리에서 공개 키id_rsa.pub
를 찾습니다.additional_packages
: 배열 형식으로 설치할 추가 패키지의 이름을 추가합니다. 배열의 각 패키지는 작은 따옴표로 묶고 쉼표로 구분해야 합니다.container-tools
와 같은 appstream 모듈을 설치하는 경우 배열은['git',' @container-tools:ol8']
와 같이 표시됩니다.
-
플레이북 파일을 만듭니다.
cat << EOF | tee ~/ol-playbook/setup.yml > /dev/null --- - hosts: all become: yes vars_files: - vars/defaults.yml tasks: - name: Generate new ssh keypair community.crypto.openssh_keypair: path: "~/.ssh/{{ ssh_key_file }}" size: 2048 comment: olam ssh keypair become: true become_user: "{{ username }}" delegate_to: localhost - name: Add a user account with access to sudo ansible.builtin.user: name: "{{ username }}" password: "{{ user_default_password | password_hash('sha512') }}" comment: Default Oracle user groups: wheel append: yes update_password: on_create - name: Set the authorized key for the user using a local public key file ansible.posix.authorized_key: user: "{{ username }}" state: present key: "{{ ssh_private_key_file }}" - name: install additional packages ansible.builtin.dnf: name: "{{ additional_packages }}" state: latest EOF
플레이북의 특정 작업 및 모듈 이름은 플레이북을 자체 문서화하도록 만드는 것을 목표로 합니다. 이 항목은 재생을 실행하는 위치 및 사용자를 지시합니다.
hosts: all
: 이 행은 인벤토리에서 작업을 실행할 호스트를 지정합니다.become: yes
: 기본적으로sudo
권한으로 실행되도록 이 섹션 내의 작업을 지시합니다.vars_files
" 이 지시어는 이 튜토리얼의 플레이북 구성을 포함하는 변수 파일을 로드합니다.
필요한 모음 설치
ansible-core
패키지에는 ansible.builtin
모음이라는 호스트를 관리하기 위한 최소 모듈 세트가 포함되어 있습니다. 모음은 대상 작업을 수행하는 플레이북, 롤, 모듈 또는 플러그인을 배포하는 방법입니다. ansible-core
를 사용하려면 내장 외부에 필요한 모듈 또는 모음을 다운로드하고 설치해야 합니다.
위의 플레이북은 ansible.posix
컬렉션을 사용하므로 이 컬렉션 및 기타 컬렉션을 설치해야 합니다. 이 작업을 수행하는 가장 쉬운 방법은 모든 종속성을 포함하는 요구 사항 파일을 만드는 것입니다.
-
요구 사항 파일을 만듭니다.
cat << 'EOF' | tee ~/ol-playbook/requirements.yml > /dev/null --- collections: - name: ansible.posix - name: community.crypto EOF
-
모음을 설치합니다.
ansible-galaxy collection install -r ~/ol-playbook/requirements.yml
출력은 Galaxy 사이트에서 압축 된 아카이브 파일을 검색 한 다음
.ansible/collections.
아래의 홈 디렉토리에 설치하는 프로세스를 보여줍니다.주: 출력에
ERROR: Ansible requires the locale encoding to be UTF-8; Detected None.
가 표시되면ansible
에 대한 로케일 설정이 잘못되었음을 나타냅니다. 다음 두 가지 환경 변수를 설정하여 문제를 해결합니다.export LC_ALL="en_US.UTF-8" export LC_CTYPE="en_US.UTF-8"
플레이북 실행
플레이북을 실행하기 전에 관리하려는 원격 Oracle Linux 인스턴스를 가리키는 이 프로젝트에 대한 인벤토리 파일을 생성해야 합니다.
-
원격 시스템의 변수에 ol-host의 IP 주소를 지정합니다.
이 변수를 사용하면 재고 파일 생성을 보다 쉽게 스크립팅할 수 있습니다.
export REMOTE=<ip_address_of_instance>
-
새 인벤토리 파일 프로젝트 디렉토리를 생성합니다.
cat << EOF | tee ~/ol-playbook/inventory > /dev/null [production] ol-host ansible_host=$REMOTE EOF
-
프로젝트 작업 디렉토리로 변경합니다.
cd ~/ol-playbook
-
ad hoc
ping
명령을 사용하여 연결을 테스트합니다.ansible ol-host -i ~/ol-playbook/inventory -m ping -u opc
-u
: ssh 접속에 대한 사용자 이름을 전달합니다. 무료 실습 환경에서는 OCI(Oracle Cloud Infrastructure)의 Oracle Linux 인스턴스에 제공된 기본 사용자인opc
사용자를 사용합니다. 계속하려면 프롬프트에yes
를 입력하여 ECDSA 키 지문을 승인합니다.
명령은 표시된 것과 유사한 결과를 사용하여 성공적으로 실행됩니다.
출력 예:
ol-host | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" }
-
Playbook을 실행합니다.
ansible-playbook -i inventory setup.yml -u opc
각 플레이의 변경된 결과를 보여주는 명령이 성공적으로 실행되어야 합니다.
원격 호스트에 접속
플레이북이 성공적으로 실행되면 username 변수에 정의된 대로 사용자를 사용하여 원격 시스템에 연결할 수 있습니다.
-
SSH를 통해 ol-host 시스템에 연결합니다.
ssh oracle@$REMOTE
ssh_key_file
변수의 값을 변경한 경우 `-i' 옵션을 ssh로 전달하고 지정된 쌍의 개인 키 파일을 가리켜야 합니다.예:
ssh -i ~/.ssh/<local_ssh_private_key> <username>@<ip_address_of_host>
-
요청된 패키지의 설치를 확인합니다.
로그인한 후 플레이북이
git
패키지를 설치했는지 확인할 수 있습니다.git --version
-
원격 호스트에서 접속을 해제합니다.
exit
저장소 추가
Oracle Linux는 미션 크리티컬 애플리케이션을 배포할 수 있는 안전하고 확장 가능하며 안정적인 플랫폼을 제공합니다. 추가 저장소를 추가하면 새 Oracle 제품 또는 타사 응용 프로그램을 설치할 수 있습니다. Oracle Linux는 이러한 추가 저장소의 프로비전을 처리하는 추가 패키지를 제공합니다.
Oracle Linux YUM 서버는 Oracle이 제공하는 다양한 저장소에 대한 세부정보를 제공합니다.
-
기존 플레이북 파일에 이러한 추가 작업을 추가합니다.
cat << EOF | tee -a setup.yml > /dev/null - name: Add the EPEL repository ansible.builtin.dnf: name: oracle-epel-release-el8 state: present when: - ansible_distribution == 'OracleLinux' - ansible_facts['distribution_major_version'] == '8' - name: Install the htop utility package ansible.builtin.dnf: name: htop enablerepo: ol8_developer_EPEL state: present when: - ansible_distribution == 'OracleLinux' - ansible_facts['distribution_major_version'] == '8' EOF
이 플레이북은 정의된 플레이 내에서 두 가지 작업을 추가합니다. 첫번째 저장소는 Oracle Linux 인스턴스의 기존 소프트웨어 저장소에 EPEL 저장소를 추가합니다. 한편 두번째 재생은 htop 패키지를 동일한 대상 호스트 인스턴스에 설치합니다.
주목할 만한 매개변수:
state: present
: 참조된 패키지가 시스템에 이미 있거나dnf
를 사용하여 설치되었는지 확인합니다.enablerepo
: 현재 작업 기간 동안에만 특정 저장소(사용 안함으로 설정된 경우)를 사용으로 설정합니다.
-
추가 작업을 수행하려면 플레이북을 다시 실행합니다.
ansible-playbook -i inventory setup.yml -u opc
플레이북이 성공적으로 완료되어 무시할 수 있는 몇 가지 경고를 표시합니다. 플레이북은 ok 또는 changed 상태로 제대로 완료된 작업을 보고합니다. changed 상태는 패키지가 있는지 확인하기보다는 패키지의 최신 버전을 요청할 때 htop 패키지를 추가하거나 dnf 캐시를 업데이트하여 플레이북이 호스트를 수정했음을 나타냅니다. ok는 작업 완료를 나타내며 아무 작업도 필요하지 않습니다.
-
저장소 및 패키지 설치를 확인합니다.
ssh oracle@$REMOTE which htop
-
htop
명령을 실행합니다.ssh oracle@$REMOTE -t htop
htop에는 대화식 셸이 필요하므로 SSH 명령의
-t
옵션은 TTY 할당을 강제로 수행합니다. -
q
를 입력하여 htop 프로그램을 종료합니다.
파일 및 디렉토리 추가, 업데이트 및 삭제
-
다음을 기존 플레이북 파일에 추가하여 디렉토리와 파일을 만듭니다.
cat << EOF | tee -a setup.yml > /dev/null - name: Create an example directory (if it does not already exist) ansible.builtin.file: path: "/home/{{ username }}/example" state: directory mode: '0755' become_user: "{{ username }}" - name: Create an empty file ansible.builtin.file: path: "/home/{{ username }}/example/test.txt" state: touch # mode: u=rw,g=r,o=r become_user: "{{ username }}" EOF
이 플레이북은 정의된 플레이 내에서 두 가지 작업을 추가합니다. 첫번째 디렉토리는 원격 사용자의 홈 디렉토리에 example이라는 새 디렉토리를 만들고, 두번째 디렉토리는 새로 만든 example 디렉토리에 test.txt이라는 빈 파일을 만듭니다.
주목할 만한 추가 매개변수:
state:
: ansible.builtin.file 모듈은 absent, directory, file, hard, link 및 touch 매개변수를 지원합니다. 이러한 작업은 디렉토리 경로가 없는 경우 만드는 directory와 비어 있는 파일이 없는 경우 만드는 touch를 사용합니다.mode
: 생성된 객체에 대한 파일 시스템 권한을 설정합니다. 명령줄을 사용할 때는 8진수 0644 또는 심볼릭 u=rw,g=r,o=r 모드를 사용할 수 있습니다. mode: 매개변수를 생략하면 시스템 기본 모드가 적용됩니다.become_user: ""
: Oracle Linux Automation Engine 권한 에스컬레이션 기능을 사용하여 작업을 실행합니다. 이전 사용지침서에서는 작업을 root로 실행하는become:
권한 에스컬레이션 기능을 도입했습니다. 이 예제에서는 유사한 기능을 사용하여 작업을 다른 사용자로 실행하는 방법을 보여줍니다. 사용자를""
변수로 설정합니다. 이 변수는 vars/defaults.yml 파일에서 oracle 사용자로 미리 정의됩니다. 따라서 이 작업은 oracle 사용자로 실행되고 해당 사용자의 기본값을 상속합니다.
-
추가 작업을 수행하려면 플레이북을 실행합니다.
ansible-playbook -i inventory setup.yml -u opc
-
새 디렉토리와 파일이 원격 호스트에 있는지 확인합니다.
ssh oracle@$REMOTE ls -al example
출력은 새 디렉토리와 빈 파일의 생성을 확인합니다. test.txt 파일은 0바이트를 가지며 oracle 사용자가 소유합니다.
-
여러 파일 추가.
시스템을 설정하는 동안 디렉토리 내에 여러 파일을 만들어야 할 수 있습니다. 개별 작업을 사용하는 대신 디렉토리에 원자 작업으로 여러 파일을 생성할 수 있습니다.
cat << EOF | tee -a setup.yml > /dev/null - name: Add multiple files ansible.builtin.file: path: "/home/{{ username }}/example/{{ item }}" state: touch with_items: - file01.txt - file02.txt - file03.txt - file04.txt become_user: "{{ username }}" EOF
이 작업은 루프를 사용하여 목록에서 여러 개의 빈 파일을 만듭니다.
path:
: 파일을 쓸 원격 시스템의 위치를 정의합니다. Oracle Linux Automation Engine은 런타임 중에 `` 변수를with_items
매개변수의 각item
으로 바꿉니다.with_items:
: 이 매개변수는 이 태스크 실행 중 루프오버할items
목록을 나타냅니다. 이 예에서는 네 개의 파일 이름만 사용하지만 필요한 만큼 목록을 만들 수 있습니다.
-
추가 작업을 수행하려면 플레이북을 실행합니다.
ansible-playbook -i inventory setup.yml -u opc
-
새 디렉토리와 파일이 원격 호스트에 있는지 확인합니다.
ssh oracle@$REMOTE ls -al example
출력에는 원본 test.txt 파일과 새 파일이 표시됩니다.
-
파일에 단일 콘텐츠 행을 추가합니다.
cat << 'EOF' | tee -a setup.yml > /dev/null - name: Insert some text into the test.txt file ansible.builtin.lineinfile: path: "/home/{{ username }}/example/test.txt" line: This is a test create: True owner: "{{ username }}" group: "{{ username }}" mode: '0644' - name: Add an extra line after the line just inserted ansible.builtin.lineinfile: path: "/home/{{ username }}/example/test.txt" regexp: '^a test' insertafter: 'This is a test' line: This is some updated text that was added later. create: True owner: "{{ username }}" group: "{{ username }}" mode: '0644' - name: Get the contents of the test.txt file ansible.builtin.command: cat ~/example/test.txt register: command_output become_user: "{{ username }}" - name: Print the results of the cat command ansible.builtin.debug: msg: "{{ command_output }}" - name: Print only the lines added to the text file ansible.builtin.debug: msg: "{{ command_output.stdout_lines }}" EOF
-
처음 두 작업은 ansible.builtin.lineinfile를 사용하여 파일에서 단일 행을 추가하고 업데이트합니다.
-
나머지 작업에는 변경 사항을 확인하는 메소드가 표시됩니다. 이 플레이북은 먼저 ansible.builtin.command를 사용하여 업데이트된 파일의 컨텐츠를 stdout으로 출력하고 이를
register
변수의 메모리에 저장합니다. 그런 다음 ansible.builtin.debug 모듈을 사용하여 해당 컨텐츠를 재생 설명서 출력의 일부로 화면에 인쇄합니다. 두번째 디버그 작업은 이전 작업에서 JSON 출력의 특정 부분으로만 출력을 제한하는 방법을 보여줍니다.
-
-
추가 작업을 수행하려면 플레이북을 실행합니다.
ansible-playbook -i inventory setup.yml -u opc
플레이북의 마지막 작업은 debug 모듈을 사용하여 파일의 내용을 인쇄합니다.
-
파일에 여러 줄의 콘텐츠를 추가합니다.
cat << EOF | tee -a setup.yml > /dev/null - name: Add two lines into test.txt ansible.builtin.blockinfile: path: "/home/{{ username }}/example/test.txt" insertafter: 'This is some updated text that was added later.' block: | "Welcome to {{ ansible_hostname }}" "Last updated on {{ ansible_date_time.iso8601 }}" - name: Create a new file and insert content into it ansible.builtin.copy: content: | === The text below was added by Oracle Linux Automation Engine ========== Hello from the ansible.builtin.copy module. This task is an example of inserting multiple lines into a text file. You can insert more lines if you want. === The text above was added by Oracle Linux Automation Engine =========== dest: "/home/{{ username }}/example/testing.txt" mode: '0644' become_user: "{{ username }}" EOF
-
추가 작업을 수행하려면 플레이북을 실행합니다.
ansible-playbook -i inventory setup.yml -u opc
-
파일에 두 개의 새 행이 있는지 확인합니다.
ssh oracle@$REMOTE cat example/test.txt
출력에는 추가된 내용이 표시되며 런타임 중 플레이북에서 수행한 자동 팩트 수집을 기반으로 작업의 변수 값이 원격 호스트에 대해 해석됩니다.
-
새 파일 생성을 확인하고 콘텐츠를 추가합니다.
ssh oracle@$REMOTE cat example/testing.txt
-
파일 및 디렉토리를 삭제합니다.
Oracle Linux Automation Engine은 파일 및 디렉토리를 생성하고 텍스트를 추가하는 것 외에도 이러한 항목을 삭제할 수 있습니다. 디렉토리와 이 플레이북이 만든 모든 파일을 제거하고 시스템을 시작할 때와 동일한 상태로 둡니다. 이러한 작업은 먼저 absent로 설정된 state: 매개변수를 사용하여 파일 목록과 디렉토리를 제거합니다. 두번째 작업은 디렉토리와 함께 한 번에 파일 제거를 처리합니다. 그러나 Oracle Linux Automation Engine 기능에 대한 추가 데모를 위한 첫 번째 작업이 포함되어 있습니다.
cat << EOF | tee -a setup.yml > /dev/null - name: Delete multiple files ansible.builtin.file: path: '{{ item }}' state: absent with_items: - "/home/{{ username }}/example/test.txt" - "/home/{{ username }}/example/file01.txt" - "/home/{{ username }}/example/file02.txt" - "/home/{{ username }}/example/file03.txt" - "/home/{{ username }}/example/file04.txt" - "/home/{{ username }}/example/testing.txt" - name: Recursively remove directory ansible.builtin.file: path: "/home/{{ username }}/example" state: absent EOF
표시된 두번째 작업을 사용하여 디렉토리 및 모든 내용을 순환적으로 삭제할 수 있습니다. 파일을 개별적으로 삭제하는 첫 번째 작업은 파일을 삭제하려는 경우에만 제공됩니다. 파일과 파일 및 디렉토리를 모두 삭제하려는 경우 두번째 작업이 더 효율적입니다.
-
추가 작업을 수행하려면 플레이북을 실행합니다.
ansible-playbook -i inventory setup.yml -u opc
-
지정된 디렉토리의 제거를 확인합니다.
ssh oracle@$REMOTE ls
출력이 부족하면 지정된 디렉토리가 확인되고 이 자습서 중에 생성된 파일이 사라집니다.
다음 단계
이렇게 멀리 만들어주신 것을 축하드립니다. 이 사용지침서에서는 Oracle Linux Automation Engine이 패키지 설치, 파일 및 디렉토리 생성, 업데이트 및 삭제, Oracle Linux에 사용자 계정 추가와 같은 자동화를 통해 Oracle Linux에서 일상적인 작업을 수행하는 여러 가지 방법을 소개했습니다. 또한 Playbook을 실행하는 동안 Oracle Linux Automation Engine의 ansible.builtin.debug
모듈을 사용하여 정보를 터미널로 반환하는 방법을 보여주었습니다. 이러한 기술로, 당신은 앞으로 벤처하고 자신의 플레이 북을 작성하는 머리 시작이있다.
관련 링크
추가 학습 자원
docs.oracle.com/learn에서 다른 실습을 탐색하거나 Oracle Learning YouTube 채널에서 더 많은 무료 학습 콘텐츠에 액세스하세요. 또한 Oracle Learning Explorer가 되려면 education.oracle.com/learning-explorer을 방문하십시오.
제품 설명서는 Oracle Help Center를 참조하십시오.
Run Oracle Linux Tasks with Oracle Linux Automation Engine
F49870-05
Copyright ©2021, Oracle and/or its affiliates.