Christopher Lam
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Advanced Storage for OpenStack Training Homework This are the procedures to complete the homework for the OpenStack Advanced Storage Training from Red Hat. ## Scenario Acme Consolidated has engaged with Red Hat to implement an OpenStack environment. They have defined the following implementation criteria. :::info **A single RHOSP 13 Controller Node running the core RHOSP services with two RHOSP 13 Compute nodes** ::: ***Solution*** OpenStack 13 Advanced Storage Lab already has this. #### The workstation node root user should have a properly configured keystonerc_admin file for administrative access to the OpenStack cluster ***Solution*** SSH to the workstation node. Generat the SSH key ```bash [root@workstation-d4f5 ~]# ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): /root/.ssh/id_rsa already exists. Overwrite (y/n)? y Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /root/.ssh/id_rsa. Your public key has been saved in /root/.ssh/id_rsa.pub. The key fingerprint is: SHA256:JJtvyHTAjC0bD4fcuDU+oC6Knb9ZYVSF5sN9jz5vWyg root@workstation-d4f5.rhpds.opentlc.com The key's randomart image is: +---[RSA 2048]----+ | .o. | | . O .o | | X %+.. | | . & B+ . . | | . o X S. . o | | . + * . .. | |. . + o .E . .| |oo . o . o... | |o o.+. +o. | +----[SHA256]-----+ [root@workstation-d4f5 ~]# ``` Copy the SSH key to control node with passward ==r3dh4t1!==. ```bash [root@workstation-d4f5 ~]# ssh-copy-id root@ctrl01 /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub" /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys root@ctrl01's password: Number of key(s) added: 1 Now try logging into the machine, with: "ssh 'root@ctrl01'" and check to make sure that only the key(s) you wanted were added. [root@workstation-d4f5 ~]# ``` Copy the keystonerc_admin file from crtl01 to the workstation. ```bash [root@workstation-d4f5 ~]# scp ctrl01:~/keystonerc_admin . keystonerc_admin 100% 327 198.7KB/s 00:00 ``` :::info **The workstation node should have the python-openstackclient package installed** ::: ***Solution*** Install OpenStack client ```shell [root@workstation-d4f5 ~]# yum install -y python2-openstackclient ``` Test out the client and credentials. ```bash [root@workstation-d4f5 ~]# source keystonerc_admin [root@workstation-d4f5 ~(keystone_admin)]# openstack service list +----------------------------------+------------+--------------+ | ID | Name | Type | +----------------------------------+------------+--------------+ | 202f5c7a826c4fc7b2d2b592559dd03e | swift | object-store | | 37704169b33b42b6896b68df92929f8b | cinder | volume | | 3cb9980330ae49178357937e5905e33a | cinderv3 | volumev3 | | 43ee8cd030e847a590fac6b38dfab0ed | cinderv2 | volumev2 | | 57f4ff0a11b64af7b748c3f26e9f31e8 | gnocchi | metric | | 5c518727354a4535a878223ed26e66ae | glance | image | | 9c7705f7419e4d42b72ca4ea4d37d829 | aodh | alarming | | a162bde594804b0dac271269093112e9 | keystone | identity | | bb5ca8b4508a43d8b9cc631b390d7a0c | ceilometer | metering | | d6cff27922cb43fba287cd0932ce514f | neutron | network | | e6c2a5ea1e654d2daf54b424286304f5 | nova | compute | | e91eb168baed40a2a29ef940a03d3dbc | placement | placement | +----------------------------------+------------+--------------+ [root@workstation-d4f5 ~(keystone_admin)]# ``` :::info **There should be an user named swift-user in a new project named swift-project created in OpenStack** ::: ***Solution*** Create the OpenStack Project. ```bash [root@workstation-d4f5 ~(keystone_admin)]# openstack project create --description "Swift Project" swift-project +-------------+----------------------------------+ | Field | Value | +-------------+----------------------------------+ | description | Swift Project | | domain_id | default | | enabled | True | | id | bc0d92bdcf264f95b2be622e72e9eb8f | | is_domain | False | | name | swift-project | | parent_id | default | | tags | [] | +-------------+----------------------------------+ [root@workstation-d4f5 ~(keystone_admin)]# ``` Create the OpenStack user. ```bash [root@workstation-d4f5 ~(keystone_admin)]# openstack user create --project swift-project --password r3dh4t1! swift-user +---------------------+----------------------------------+ | Field | Value | +---------------------+----------------------------------+ | default_project_id | bc0d92bdcf264f95b2be622e72e9eb8f | | domain_id | default | | enabled | True | | id | 8ad5de3b53cd473ebbd613df09f7af19 | | name | swift-user | | options | {} | | password_expires_at | None | +---------------------+----------------------------------+ [root@workstation-d4f5 ~(keystone_admin)]# ``` Add the user role. ```bash [root@workstation-d4f5 ~(keystone_admin)]# openstack role add --user swift-user --project swift-project _member_ ``` :::info **The workstation node root user should have a properly configured keystonerc_user file for access to the OpenStack cluster as swift-user** ::: ***Solution*** Create the keystonerc_user file with the following contents. ```bash export OS_USERNAME=swift-user export OS_PASSWORD=r3dh4t1! export OS_AUTH_URL=http://172.16.7.50:5000/v3 export PS1='[\u@\h \W(swift-user)]\$ ' export OS_PROJECT_NAME=swift-project export OS_REGION_NAME=RegionOne export OS_USER_DOMAIN_NAME=Default export OS_PROJECT_DOMAIN_NAME=Default export OS_IDENTITY_API_VERSION=3 ``` :::info **A Ceph Storage cluster running on 3 nodes, each containing 2 disk devices The 3 (three) Ceph MON nodes should be configured on different nodes than OSD nodes Ceph should be configured so that your OPENTLC account has administrative access to ceph A RADOS gateway running on MON nodes** ::: ***Solution*** **Install and configure docker-distribution** Install docker-distribution, skopeo and jq: ```bash [root@workstation ~]# yum -y install docker-distribution skopeo jq ``` Replace the contents of /etc/docker-distribution/registry/config.yml with those shown below: ```yaml= version: 0.1 log: fields: service: registry storage: cache: layerinfo: inmemory filesystem: rootdirectory: /var/lib/registry http: addr: 0.0.0.0:5000 host: https://workstation.example.com:5000 tls: certificate: /etc/docker-distribution/my_self_signed_cert.crt key: /etc/docker-distribution/my_self_signed.key ``` Create a certificate for the Docker registry: ```bash [root@workstation-d4f5 ~]# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/docker-distribution/my_self_signed.key -out /etc/docker-distribution/my_self_signed_cert.crt ``` ```bash Generating a 2048 bit RSA private key .......+++ ..........................................+++ writing new private key to '/etc/mycerts/my_self_signed.key' ----- You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [XX]:HK State or Province Name (full name) []:Hong Kong Locality Name (eg, city) [Default City]:HK Organization Name (eg, company) [Default Company Ltd]:Red Hat Organizational Unit Name (eg, section) []:Lab Common Name (eg, your name or your server's hostname) []:workstation.example.com Email Address []:root@workstation.example.com ``` Enable and start the Docker distribution service: ```bash [root@workstation ~]# systemctl enable docker-distribution --now ``` Configure RHEL to trust the self-signed certificate: Using the openssl utility, create a PEM formatted version of my_self_signed_cert.crt that was created ```bash [root@workstation-d4f5 ~]# openssl x509 -in /etc/docker-distribution/my_self_signed_cert.crt -out /etc/pki/ca-trust/source/anchors/workstation.pem -outform PEM ``` Update the system’s Trust Store using update-ca-trust: ```bash [root@workstation ~]# update-ca-trust ``` Copy a container image, prior to the latest, from Red Hat to our local registry using skopeo: ```bash [root@workstation ~]# skopeo copy \ docker://registry.access.redhat.com/rhceph/rhceph-3-rhel7:latest \ docker://workstation.example.com:5000/rhceph/rhceph-3-rhel7:latest ``` Install the ceph-ansible and ceph-common package on the workstation host: ```bash [root@workstation ~]# yum -y install ceph-ansible ceph-common ``` Create an Ansible hosts inventory file called ~/ceph-nodes. ```bash [root@workstation-d4f5 ~]# vi ceph-nodes ``` ```bash [ceph] ceph-node01 ceph-node02 ceph-node03 ceph-mon01 ceph-mon02 ceph-mon03 ``` Copy all SSH keys too all nodes for authentication. Using password r3dh4t1! ```bash [root@workstation-d4f5 ~]# sed 1d ceph-nodes | xargs -i ssh-copy-id root@{} ``` Create an Ansible Playbook called ceph-preqs.yml with the following content. ```bash [root@workstation-d4f5 ~]# vi ceph-preqs.yml ``` ```yaml= --- - name: Ceph Installation Pre-requisites hosts: all gather_facts: no vars: remote_user: root ignore_errors: yes tasks: - name: Create the admin user user: name: admin generate_ssh_key: yes ssh_key_bits: 2048 ssh_key_file: .ssh/id_rsa password: $6$mZKDrweZ5e04Hcus$97I..Zb0Ywh1lQefdCRxGh2PJ/abNU/LIN7zp8d2E.uYUSmx1RLokyzYS3mUTpipvToZbYKyfMqdP6My7yYJW1 - name: Create sudo file for admin user lineinfile: path: /etc/sudoers.d/admin state: present create: yes line: "admin ALL=(root) NOPASSWD:ALL" owner: root group: root mode: 0440 - name: Push ssh key to hosts authorized_key: user: admin key: "{{ lookup('file', '/root/.ssh/id_rsa.pub') }}" state: present - name: Copy certificate file from local registry copy: src: /etc/pki/ca-trust/source/anchors/workstation.pem dest: /etc/pki/ca-trust/source/anchors/workstation.pem - name: Update system's trust store with new certificate shell: /usr/bin/update-ca-trust - name: Get registry catalog to validate certificate trust get_url: url: https://workstation.example.com:5000/v2/_catalog dest: /tmp/registry-catalog.json ``` Run the ceph-preqs.yml playbook: ```bash [root@workstation ~]# ansible-playbook -i ceph-nodes ceph-preqs.yml ``` Update the root user's SSH config file with new hosts. ```bash [root@workstation-d4f5 ~]# vi ~/.ssh/config ``` ```bash Host ceph-node01 Hostname ceph-node01 User admin Host ceph-node02 Hostname ceph-node02 User admin Host ceph-node03 Hostname ceph-node03 User admin Host ceph-mon01 Hostname ceph-mon01 User admin Host ceph-mon02 Hostname ceph-mon02 User admin Host ceph-mon03 Hostname ceph-mon03 User admin ``` Change the working directory to the ceph-ansible installation directory: ```bash [root@workstation ~]# cd /usr/share/ceph-ansible/ ``` Create the Ansible inventory file: ```bash [root@workstation ceph-ansible]# vi hosts ``` ```bash [mons] ceph-node0[1:3] [mgrs] ceph-node0[1:3] [osds] ceph-node0[1:3] [rgws] ceph-mon0[1:3] ``` Create the ansible.cfg file for Ansible: ```bash [root@workstation ceph-ansible]# mv ansible.cfg ansible.cfg.orig [root@workstation ceph-ansible]# vi ansible.cfg ``` ```bash [defaults] inventory = /usr/share/ceph-ansible/hosts action_plugins = /usr/share/ceph-ansible/plugins/actions roles_path = /usr/share/ceph-ansible/roles log_path = /var/log/ansible.log timeout = 60 host_key_checking = False retry_files_enabled = False retry_files_save_path = /usr/share/ceph-ansible/ansible-retry [privilege_escalation] become=True become_method=sudo become_user=root become_ask_pass=False [ssh_connection] # see: https://github.com/ansible/ansible/issues/11536 control_path = %(directory)s/%%h-%%r-%%p ``` Create the all.yml file: ```bash [root@workstation ceph-ansible]# vi group_vars/all.yml ``` ```yaml= --- ### # General Options ### cluster: ceph fetch_directory: /usr/share/ceph-ansible/ceph-ansible-keys ntp_service_enabled: true ### # Ceph Configuration Overrides ### ceph_conf_overrides: global: mon_osd_allow_primary_afinity: true osd_pool_default_size: 2 osd_pool_default_min_size: 1 mon_pg_warn_min_per_osd: 0 mon_pg_warn_max_per_osd: 0 mon_pg_warn_max_object_skew: 0 client: rbd_default_features: 1 rbd_default_format: 2 ### # Client Options ### rbd_cache: "true" rbd_cache_writethrough_until_flush: "false" ### # OSD Options ### journal_size: 1024 public_network: 192.168.99.0/24 cluster_network: 192.168.56.0/24 osd_scenario: non-collocated ### # Monitor Options ### monitor_address_block: 192.168.99.0/24 ### # RADOSGW Options ### radosgw_address_block: 192.168.99.0/24 ### # Docker ### ceph_docker_image: rhceph/rhceph-3-rhel7 ceph_docker_image_tag: latest ceph_docker_registry: workstation.example.com:5000 containerized_deployment: true ``` Create the osds.yml file: ```bash [root@workstation ceph-ansible]# vi group_vars/osds.yml ``` ```yaml= --- copy_admin_key: true devices: - /dev/vdb - /dev/vdc dedicated_devices: - /dev/vdd - /dev/vdd ceph_osd_docker_memory_limit: 1g ceph_osd_docker_cpu_limit: 1 ``` Create the mons.yml file: ```bash [root@workstation ceph-ansible]# vi group_vars/mons.yml ``` ```yaml= --- ceph_mon_docker_memory_limit: 1g ceph_mon_docker_cpu_limit: 1 ``` Create the mgrs.yml file: ```bash [root@workstation ceph-ansible]# vi group_vars/mgrs.yml ``` ```yaml= --- ceph_mgr_docker_memory_limit: 1g ceph_mgr_docker_cpu_limit: 1 ``` Create the rgws.yml file: ```bash [root@workstation-d4f5 ceph-ansible]# vi group_vars/rgws.yml ``` ```yaml= --- dummy: ceph_rgw_docker_memory_limit: 1g ceph_rgw_docker_cpu_limit: 1 ``` Create the site-docker.yml playbook: ```bash [root@workstation ceph-ansible]# cp site-docker.yml.sample site-docker.yml ``` Invoke the site-docker.yml playbook to deploy the Ceph cluster: ```bash [root@workstation ceph-ansible]# ansible-playbook -i hosts site-docker.yml ``` Copy the Ceph configuration and the administrative keyring to the workstation host: ```bash [root@workstation ~]# scp root@ceph-node01:/etc/ceph/ceph.conf /etc/ceph/ [root@workstation ~]# scp root@ceph-node01:/etc/ceph/ceph.client.admin.keyring /etc/ceph/ ``` Examine the Ceph cluster status and verify access to the cluster: ```bash [root@workstation ~]# ceph -s ``` :::info **A Swift Storage cluster running on the differents nodes as Ceph, each also configured with 2 disk devices** **An additonal storage policy named FOUR should be created, requiring 4 replicas of all objects and using 6 filesystems on 3 nodes.** ::: ***Solution*** Create the ~/rsyncd.conf file. ```bash [root@workstation-d4f5 ~]# vi ~/rsyncd.conf ``` Enure the global section contains these entries: ```bash ##assumes 'swift' has been used as the Object Storage user/group uid = swift gid = swift log file = /var/log/rsyncd.log pid file = /var/run/rsyncd.pid ##address on which the rsync daemon listens address = <IP> [account] max connections = 2 path = /srv/node/ read only = false write only = no list = yes incoming chmod = Du=rwx,g=rx,o=rx,Fu=rw,g=r,o=r outgoing chmod = Du=rwx,g=rx,o=rx,Fu=rw,g=r,o=r lock file = /var/lock/account.lock [container] max connections = 2 path = /srv/node/ read only = false write only = no list = yes incoming chmod = Du=rwx,g=rx,o=rx,Fu=rw,g=r,o=r outgoing chmod = Du=rwx,g=rx,o=rx,Fu=rw,g=r,o=r lock file = /var/lock/container.lock [object] max connections = 2 path = /srv/node/ read only = false write only = no list = yes incoming chmod = Du=rwx,g=rx,o=rx,Fu=rw,g=r,o=r outgoing chmod = Du=rwx,g=rx,o=rx,Fu=rw,g=r,o=r lock file = /var/lock/object.lock ``` Copy the rsyncd.conf to all storage nodes. ```bash [root@workstation-d4f5 ~]# grep stor swift-nodes | xargs -i scp rsyncd.conf root@{}:/etc ``` Replace the IP in the rsyncd.conf to the according storage node. ```bash [root@workstation-d4f5 ~]# ssh root@stor01 "sed -i 's/<IP>/172.16.7.21/' /etc/rsyncd.conf && cat /etc/rsyncd.conf" [root@workstation-d4f5 ~]# ssh root@stor02 "sed -i 's/<IP>/172.16.7.22/' /etc/rsyncd.conf && cat /etc/rsyncd.conf" [root@workstation-d4f5 ~]# ssh root@stor03 "sed -i 's/<IP>/172.16.7.23/' /etc/rsyncd.conf && cat /etc/rsyncd.conf" ``` Enable and start the rsyncd system service on each Swift storage node: ```bash [root@workstation-d4f5 ~]# grep stor swift-nodes | xargs -i ssh root@{} "systemctl enable rsyncd" [root@workstation-d4f5 ~]# grep stor swift-nodes | xargs -i ssh root@{} "systemctl start rsyncd" ``` Install the software packages on the Swift storage nodes: ```bash [root@workstation-d4f5 ~]# grep stor swift-nodes | xargs -i ssh root@{} "yum -y install openstack-swift-object openstack-swift-container openstack-swift-account memcached" ``` Install the software on the Swift proxy nodes: ```bash [root@workstation-d4f5 ~]# grep proxy swift-nodes | xargs -i ssh root@{} "yum -y install openstack-swift-proxy memcached python-memcached" ``` Create a random hash value for Swift’s hash_prefix and hash_suffix: ```bash [root@workstation ~]$ openssl rand -hex 8 | tee hash_prefix [root@workstation ~]$ openssl rand -hex 8 | tee hash_suffix ``` Create swift.conf. ```bash [root@workstation-d4f5 ~]# vi swift.conf ``` With the following content. Please replace the prefix and suffix with your own prefix and suffix. ```bash [swift-hash] swift_hash_path_prefix = e693e289a2e716a0 swift_hash_path_suffix = 47910b9fd8c8401f [storage-policy:0] name = policy-0 policy_type = replication default = yes [storage-policy:1] name = FOUR policy_type = replication ``` Copy the swift.conf file to /etc/swift/swift.conf at each node in the Swift cluster. ```bash [root@workstation ~]# cat swift-nodes | xargs -I {} -P 6 scp swift.conf root@{}:/etc/swift/swift.conf ``` Change the bind_ip for container-server.conf, account-server.conf and object-server.conf in all storage ndoes. ```bash [root@workstation-d4f5 ~]# for n in container-server.conf account-server.conf object-server.conf;do ssh root@stor01 "sed -i 's/127.0.0.1/172.16.7.21/' /etc/swift/${n} && cat /etc/swift/${n}";done [root@workstation-d4f5 ~]# for n in container-server.conf account-server.conf object-server.conf;do ssh root@stor02 "sed -i 's/127.0.0.1/172.16.7.22/' /etc/swift/${n} && cat /etc/swift/${n}";done [root@workstation-d4f5 ~]# for n in container-server.conf account-server.conf object-server.conf;do ssh root@stor03 "sed -i 's/127.0.0.1/172.16.7.23/' /etc/swift/${n} && cat /etc/swift/${n}";done ``` Install the python-swift package ```bash [root@workstation ~]$ yum -y install python-swift ``` Create a directory for creating ring files: ```bash [root@workstation ~]$ mkdir ~/swift-rings/ [root@workstation ~]$ cd ~/swift-rings/ ``` Create all 3 rings (account, container and object) ```bash [root@workstation-d4f5 swift-rings]# swift-ring-builder ./account.builder create 10 3 0 [root@workstation-d4f5 swift-rings]# swift-ring-builder ./container.builder create 10 3 0 [root@workstation-d4f5 swift-rings]# swift-ring-builder ./object.builder create 10 3 0 [root@workstation-d4f5 swift-rings]# swift-ring-builder ./object-1.builder create 10 4 0 ``` Add devices to the account service ring, 2 disks for each storage node: ```bash swift-ring-builder ./account.builder add z1-172.16.7.21:6202/b1 100 swift-ring-builder ./account.builder add z1-172.16.7.21:6202/c1 100 swift-ring-builder ./account.builder add z1-172.16.7.22:6202/b1 100 swift-ring-builder ./account.builder add z1-172.16.7.22:6202/c1 100 swift-ring-builder ./account.builder add z1-172.16.7.23:6202/b1 100 swift-ring-builder ./account.builder add z1-172.16.7.23:6202/c1 100 ``` Add devices to the container service ring, 2 disks for each storage node: ```bash swift-ring-builder ./container.builder add z1-172.16.7.21:6201/b1 100 swift-ring-builder ./container.builder add z1-172.16.7.21:6201/c1 100 swift-ring-builder ./container.builder add z1-172.16.7.22:6201/b1 100 swift-ring-builder ./container.builder add z1-172.16.7.22:6201/c1 100 swift-ring-builder ./container.builder add z1-172.16.7.23:6201/b1 100 swift-ring-builder ./container.builder add z1-172.16.7.23:6201/c1 100 ``` Add devices to the object service ring, 2 disks for each storage node: ```bash swift-ring-builder ./object.builder add z1-172.16.7.21:6200/b1 100 swift-ring-builder ./object.builder add z1-172.16.7.21:6200/c1 100 swift-ring-builder ./object.builder add z1-172.16.7.22:6200/b1 100 swift-ring-builder ./object.builder add z1-172.16.7.22:6200/c1 100 swift-ring-builder ./object.builder add z1-172.16.7.23:6200/b1 100 swift-ring-builder ./object.builder add z1-172.16.7.23:6200/c1 100 swift-ring-builder ./object-1.builder add z1-172.16.7.21:6200/b1 100 swift-ring-builder ./object-1.builder add z1-172.16.7.21:6200/c1 100 swift-ring-builder ./object-1.builder add z1-172.16.7.22:6200/b1 100 swift-ring-builder ./object-1.builder add z1-172.16.7.22:6200/c1 100 swift-ring-builder ./object-1.builder add z1-172.16.7.23:6200/b1 100 swift-ring-builder ./object-1.builder add z1-172.16.7.23:6200/c1 100 ``` Create a binary ring files. ```bash swift-ring-builder account.builder rebalance swift-ring-builder container.builder rebalance swift-ring-builder object.builder rebalance swift-ring-builder object-1.builder rebalance ``` Distribute the ring files to all Swift storage nodes: ```bash [root@workstation-d4f5 swift-rings]# sed 1d ~/nodes | xargs -i scp -r * root@{}:/etc/swift/ ``` Enable the Swift account, container and object server services: ```bash [root@workstation-d4f5 swift-rings]# sed 1d ~/nodes | xargs -i ssh root@{} systemctl enable openstack-swift-account openstack-swift-container openstack-swift-object ``` Start the Swift account, container and object server services: ```bash [root@workstation-d4f5 swift-rings]# sed 1d ~/nodes | xargs -i ssh root@{} systemctl start openstack-swift-account openstack-swift-container openstack-swift-object ``` :::info **Two Swift proxy servers running on nodes proxy01 & proxy02** ::: ***Solution*** Install the Swift client software on the workstation node: ```bash [root@workstation-d4f5 swift-rings]$ yum -y install python-swiftclient ``` Distribute the ring files to the Swift proxy nodes: ```bash [root@workstation swift-rings]$ scp -r * root@proxy01:/etc/swift/ [root@workstation swift-rings]$ scp -r * root@proxy02:/etc/swift/ ``` We will change the proxy nodes server conf using patch here. Create the proxy-server.conf.patch file ```bash [root@workstation-d4f5 ~]# vi proxy-server.conf.patch ``` Put the following contents. ```bash= --- proxy-server.conf.orig 2019-11-25 21:04:24.068917360 -0500 +++ proxy-server.conf 2019-11-25 21:06:11.521459471 -0500 @@ -8,7 +8,7 @@ # open to access by any client. This is almost always a very bad idea, and # it's overridden by OSP Director, so it is likely to go away some time # after Newton. -pipeline = catch_errors gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl ratelimit copy container-quotas account-quotas slo dlo versioned_writes proxy-logging proxy-server +#pipeline = catch_errors gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl ratelimit copy container-quotas account-quotas slo dlo versioned_writes proxy-logging proxy-server # This sample pipeline uses tempauth and is used for SAIO dev work and # testing. See below for a pipeline using keystone. @@ -17,7 +17,7 @@ # The following pipeline shows keystone integration. Comment out the one # above and uncomment this one. Additional steps for integrating keystone are # covered further below in the filter sections for authtoken and keystoneauth. -#pipeline = catch_errors gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl ratelimit authtoken keystone copy container-quotas account-quotas slo dlo versioned_writes proxy-logging proxy-server +pipeline = catch_errors gatekeeper healthcheck proxy-logging cache container_sync bulk tempurl ratelimit authtoken keystone copy container-quotas account-quotas slo dlo versioned_writes proxy-logging proxy-server [app:proxy-server] use = egg:swift#proxy @@ -39,7 +39,7 @@ [filter:cache] use = egg:swift#memcache -memcache_servers = 127.0.0.1:11211 +memcache_servers = 172.16.7.50:11211 [filter:ratelimit] use = egg:swift#ratelimit @@ -88,10 +88,14 @@ [filter:authtoken] paste.filter_factory = keystonemiddleware.auth_token:filter_factory -admin_tenant_name = %SERVICE_TENANT_NAME% -admin_user = %SERVICE_USER% -admin_password = %SERVICE_PASSWORD% -auth_host = 127.0.0.1 -auth_port = 35357 -auth_protocol = http -signing_dir = /tmp/keystone-signing-swift +paste.filter_factory = keystonemiddleware.auth_token:filter_factory +auth_uri = http://172.16.7.50:5000 +auth_url = http://172.16.7.50:35357 +memcached_servers = 172.16.7.50:11211 +auth_type = password +project_domain_id = default +user_domain_id = default +project_name = services +username = swift +password = r3dh4t1! +delay_auth_decision = True ``` Copy the patch to all proxy servers. ```bash [root@workstation-d4f5 ~]# for n in proxy01 proxy02;do scp proxy-server.conf.patch ${n}:/etc/swift/;done ``` Install patch command in both proxy servers. ```bash [root@workstation-d4f5 ~]# for n in proxy01 proxy02;do ssh ${n} "yum -y install patch";done ``` Apply the patch to the proxy-server.conf file in both proxy servers. ```bash [root@workstation-d4f5 ~]# for n in proxy01 proxy02;do ssh ${n} "patch /etc/swift/proxy-server.conf < /etc/swift/proxy-server.conf.patch";done ``` Enable memcached.service and openstack-swift-proxy.service. ```bash [root@workstation-d4f5 ~]# for n in proxy01 proxy02;do ssh ${n} "systemctl enable memcached openstack-swift-proxy";done ``` Start both services. ```bash [root@workstation-d4f5 ~]# for n in proxy01 proxy02;do ssh ${n} "systemctl start memcached openstack-swift-proxy";done ``` **Configure HA Proxy** Connect to haproxy01 ```bash [root@wokstation ~]# scp /etc/hosts haproxy01:/etc/hosts [root@workstation ~]# ssh root@haproxy01 ``` Install haproxy package ```bash [root@haproxy01 ~]# yum install -y haproxy ``` Back up the orginal haproxy.cfg ```bash [root@haproxy01 ~]# mv /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.bak ``` Confgure file /etc/haproxy/haproxy.cfg with the following content. ```bash [root@haproxy01 ~]# vi /etc/haproxy/haproxy.cfg ``` ```bash global maxconn 4096 user haproxy group haproxy daemon defaults log global mode http option httplog option dontlognull retries 3 option redispatch maxconn 2000 contimeout 5000 clitimeout 50000 srvtimeout 50000 listen swift-cluster 192.168.56.80:8080 mode http stats enable stats auth username:password balance roundrobin option httpchk HEAD /healthcheck HTTP/1.0 option forwardfor option http-server-close timeout http-keep-alive 500 server proxy01 192.168.56.24:8080 weight 5 check inter 2000 server proxy02 192.168.56.25:8080 weight 5 check inter 2000 ``` Enable and start the haproxy service ```bash [root@haproxy01 ~]# systemctl enable haproxy --now ``` Go back to workstation and auth as swift-user. ```bash [root@workstation-d4f5 ~]# source keystonerc_user [root@workstation-d4f5 ~(swift-user)]# ``` Find out the swift auth key for swift-user. It's not using the swift comes with OpenStack. Copy the AUTH key in OS_STORAGE_URL (yours will be different). ```bash [root@workstation-d4f5 ~(swift-user)]# swift auth export OS_STORAGE_URL=http://172.16.7.50:8080/v1/AUTH_bc0d92bdcf264f95b2be622e72e9eb8f export OS_AUTH_TOKEN=69dc6688cddf49c49f682f4caece978a [root@workstation-d4f5 ~(swift-user)]# ``` Edit keystonerc_user for swift-user. ```bash [root@workstation-d4f5 ~(swift-user)]# vi keystonerc_user ``` Add the OS_STORAGE_URL with your AUTH token to the end of the file as shown below. Note that the IP is different (swift cluster now). ```bash export OS_USERNAME=swift-user export OS_PASSWORD=r3dh4t1! export OS_AUTH_URL=http://172.16.7.50:5000/v3 export PS1='[\u@\h \W(swift-user)]\$ ' export OS_PROJECT_NAME=swift-project export OS_REGION_NAME=RegionOne export OS_USER_DOMAIN_NAME=Default export OS_PROJECT_DOMAIN_NAME=Default export OS_IDENTITY_API_VERSION=3 export OS_STORAGE_URL=http://192.168.56.80:8080/v1/AUTH_bc0d92bdcf264f95b2be622e72e9eb8f ``` Assign the SwiftOperator role to the created user account in the swift-users project: ```bash [root@workstation-d4f5 ~(swift-user)]# source keystonerc_admin [root@workstation-d4f5 ~(keystone_admin)]# openstack role add --user swift-user --project swift-project SwiftOperator ``` To test out if swift is working properly. ```bash [root@workstation-d4f5 ~(swift-user)]# swift auth export OS_STORAGE_URL=http://192.168.56.80:8080/v1/AUTH_bc0d92bdcf264f95b2be622e72e9eb8f export OS_AUTH_TOKEN=1a61712a8bdd482c8527ef5145d9f021 [root@workstation-d4f5 ~(swift-user)]# swift stat Account: AUTH_bc0d92bdcf264f95b2be622e72e9eb8f Containers: 0 Objects: 0 Bytes: 0 X-Put-Timestamp: 1574738975.77705 X-Timestamp: 1574738975.77705 X-Trans-Id: tx9bd7be26d03840c5b72c0-005ddc9c1e Content-Type: text/plain; charset=utf-8 X-Openstack-Request-Id: tx9bd7be26d03840c5b72c0-005ddc9c1e [root@workstation-d4f5 ~(swift-user)]# ``` :::info A container named OSPAS should be created that uses storage policy policy-0 ::: ***Solution*** Auth as swift user and issue the following command. ```bash [root@workstation-d4f5 ~(swift-user)]# swift post OSPAS ``` Make sure it's in the policy-0. ```bash [root@workstation-d4f5 ~(swift-user)]# swift stat OSPAS Account: AUTH_bc0d92bdcf264f95b2be622e72e9eb8f Container: OSPAS Objects: 0 Bytes: 0 Read ACL: Write ACL: Sync To: Sync Key: Accept-Ranges: bytes X-Storage-Policy: policy-0 Last-Modified: Tue, 26 Nov 2019 03:32:29 GMT X-Timestamp: 1574739148.74637 X-Trans-Id: txa2ca3f49cb7a4db8becce-005ddc9d76 Content-Type: application/json; charset=utf-8 X-Openstack-Request-Id: txa2ca3f49cb7a4db8becce-005ddc9d76 [root@workstation-d4f5 ~(swift-user)]# ``` :::info On the workstation node create a swiftrc file should be created in the root user’s home directory on the workstation node so that the swift-user OpenStack account should be able to write objects into a container called DATA that uses the Swift storage policy FOUR ::: ***Solution*** Create the container DATA with policy FOUR ```bash [root@workstation-d4f5 ~(swift-user)]# source keystonerc_user [root@workstation-d4f5 ~(swift-user)]# swift post --header 'X-Storage-Policy:FOUR' DATA ``` Copy the keystonerc_user to swiftrc ```bash [root@workstation-d4f5 ~(swift-user)]# cp keystonerc_user swiftrc ``` :::info **Cinder should be configured with 3 backend storage pools, all of the type RBD** **Volumes >10gb should Only be created in the cinder-large storage pool** **Volumes <5gb should Only be created in the cinder-small storage pool** **The third storage pool should be named cinder-med** **Volumes >5gb & <10gb should be spread evenly across all 3 storage pools** **Ensure that the grading script can create 5 volumes to evaluate the cinder scheduler configuration** **Cinder should NOT be dependent on a local volume group on the controller node to start** **Cinder backups should be configured use a NFS share from the server storage** ::: ***Solution*** Use RBD as backend instead of LVM From the workstation node, create a all 3 storage pools in the Ceph cluster using 128 PGs. They're called large-vol, med-vol and small-vol. ```bash ceph osd pool create large-vol 128 ceph osd pool create med-vol 128 ceph osd pool create small-vol 128 ``` Create one user to access each pool. They're cender-l, cender-m and cinder-s. ```bash ceph auth get-or-create client.cinder-l mon 'allow r' osd 'allow class-read object_prefix rbd_children, allow rwx pool=large-vol' -o /etc/ceph/ceph.client.cinder-l.keyring ceph auth get-or-create client.cinder-m mon 'allow r' osd 'allow class-read object_prefix rbd_children, allow rwx pool=med-vol' -o /etc/ceph/ceph.client.cinder-m.keyring ceph auth get-or-create client.cinder-s mon 'allow r' osd 'allow class-read object_prefix rbd_children, allow rwx pool=small-vol' -o /etc/ceph/ceph.client.cinder-s.keyring ``` Copy the cinder user’s keyring file to the /etc/ceph directory on ctrl01 to distribute the keyring: ```bash [root@workstation ~]$ ssh root@ctrl01 yum -y install ceph-common [root@workstation ~]$ scp /etc/ceph/ceph.client.cinder-*.keyring root@ctrl01:/etc/ceph/ ``` Retrieve each cinder user’s key and save it as a file in ctrl01. ```bash ceph auth get-key client.cinder-l | ssh ctrl01 tee ./client.cinder-l.key ceph auth get-key client.cinder-m | ssh ctrl01 tee ./client.cinder-m.key ceph auth get-key client.cinder-s | ssh ctrl01 tee ./client.cinder-s.key ``` Distribute the /etc/ceph/ceph.conf file to the ctrl01 node: ```bash [root@workstation ~]$ scp /etc/ceph/ceph.conf root@ctrl01:/etc/ceph ``` From the ctrl01 server, view the Ceph status to verify that you can access the cluster as the cinder user: ```bash [root@workstation ~]# ssh ctrl01 [root@ctrl01 ~]$ ceph --id cinder-l -s ``` On ctrl01, set the ceph.client.cinder-*.keyring file group to cinder to enable Cinder access to the keyring, and update the file permissions to allow group read permissions: ```bash [root@ctrl01 ~]# chgrp cinder /etc/ceph/ceph.client.cinder-*.keyring [root@ctrl01 ~]# chmod 0640 /etc/ceph/ceph.client.cinder-*.keyring ``` Generate a UUID and save it in myuuid.txt: ```bash [root@ctrl01 ~]$ uuidgen | tee ~/myuuid.txt ``` Add an [rbd] section, using the UUID previously generated for the rbd_secret_uuid parameter as the following sample code fragment shows: ```bash [root@ctrl01 ~]$ vi /etc/cinder/cinder.conf ``` Sample Fragment ==(Please aware that the UUID is the UUID you generated before)== ```bash [DEFAULT] ... default_volume_type = cinder-large enabled_backends = cinder-large,cinder-med,cinder-small ... [cinder-large] backend_host = rbd:cinder volume_driver = cinder.volume.drivers.rbd.RBDDriver volume_backend_name = ceph rbd_ceph_conf = /etc/ceph/ceph.conf rbd_pool = large-vol rbd_user = cinder-l rbd_secret_uuid = b3af1703-1e55-4139-a274-5d370ee35528 rbd_max_clone_depth = 5 rbd_store_chunk_size = 4 rbd_flatten_volume_from_snapshot = false filter_function = "5 <= volume.size" [cinder-med] backend_host = rbd:cinder volume_driver = cinder.volume.drivers.rbd.RBDDriver volume_backend_name = ceph rbd_ceph_conf = /etc/ceph/ceph.conf rbd_pool = med-vol rbd_user = cinder-m rbd_secret_uuid = b3af1703-1e55-4139-a274-5d370ee35528 rbd_max_clone_depth = 5 rbd_store_chunk_size = 4 rbd_flatten_volume_from_snapshot = false filter_function = "(5 <= volume.size) and (volume.size < 10)" [cinder-small] backend_host = rbd:cinder volume_driver = cinder.volume.drivers.rbd.RBDDriver volume_backend_name = ceph rbd_ceph_conf = /etc/ceph/ceph.conf rbd_pool = small-vol rbd_user = cinder-s rbd_secret_uuid = b3af1703-1e55-4139-a274-5d370ee35528 rbd_max_clone_depth = 5 rbd_store_chunk_size = 4 rbd_flatten_volume_from_snapshot = false filter_function = "volume.size < 10" ``` Go back to workstation, auth as keystone admin, then reate a new volume type. ```bash [root@workstation-d4f5 ~]# source keystonerc_admin [root@workstation-d4f5 ~(keystone_admin)]# openstack volume type create cinder-large [root@workstation-d4f5 ~(keystone_admin)]# openstack volume type create cinder-med [root@workstation-d4f5 ~(keystone_admin)]# openstack volume type create cinder-small ``` Restart both cinder-api and cinder-volume services so that Cinder starts using the RBD back end: ```bash [root@workstation-d4f5 ~]# ssh ctrl01 systemctl restart openstack-cinder-api openstack-cinder-volume ``` Create a file named ceph.xml for virsh to use to create a libvirt secret: ==(uuid is your uuid)== ```bash [root@ctrl01 ~]# vi ceph.xml ``` ```xml <secret ephemeral="no" private="no"> <uuid> bdbed7ec-9c95-42d9-8f52-bfc4b5baaa8e </uuid> <usage type="ceph"> <name>cinder secret</name> </usage> </secret> ``` Distribute ceph.xml to each compute node: ```bash [root@ctrl01 ~]# scp ceph.xml root@compute01:~/ [root@ctrl01 ~]# scp ceph.xml root@compute02:~/ ``` Using virsh to define a libvirt secret using the ceph.xml file created: ```bash [root@ctrl01 ~]# ssh compute01 virsh secret-define --file ~/ceph.xml [root@ctrl01 ~]# ssh compute02 virsh secret-define --file ~/ceph.xml ``` Copy the cinder client’s key, stored in a file created in a previous step, to the compute nodes: ```bash [root@ctrl01 ~]# scp client.cinder-*.key root@compute01:~/ [root@ctrl01 ~]# scp client.cinder-*.key root@compute02:~/ ``` Using the UUID generated previously, assign the proper value to the libvirt secret: ```bash ssh compute01 virsh secret-set-value --secret $(cat myuuid.txt) --base64 $(cat ~/client.cinder-l.key) ssh compute01 virsh secret-set-value --secret $(cat myuuid.txt) --base64 $(cat ~/client.cinder-m.key) ssh compute01 virsh secret-set-value --secret $(cat myuuid.txt) --base64 $(cat ~/client.cinder-s.key) ssh compute02 virsh secret-set-value --secret $(cat myuuid.txt) --base64 $(cat ~/client.cinder-l.key) ssh compute02 virsh secret-set-value --secret $(cat myuuid.txt) --base64 $(cat ~/client.cinder-m.key) ssh compute02 virsh secret-set-value --secret $(cat myuuid.txt) --base64 $(cat ~/client.cinder-s.key) ``` Configure NFS Connect to storage node ```bash [root@ctrl01 ~(keystone_admin)]# ssh storage ``` Install nfs server and configure firewall ```bash [root@storage ~]# yum install nfs-utils -y ``` Enable service and firewall. ```bash systemctl enable nfs-server --now firewall-cmd --add-service=nfs --permanent firewall-cmd --add-service=rpc-bind --permanent firewall-cmd --add-service=mountd --permanent firewall-cmd --reload ``` Configure export ```bash parted -a optimal /dev/vdb mklabel gpt mkpart primary 0% 30G mkfs.ext4 /dev/vdb1 echo "/dev/vdb1 /exports ext4 defaults 0 0" >> /etc/fstab mount -a echo "/exports 172.16.7.0/24(rw,no_root_squash)" >> /etc/exports exportfs -a ``` Go back to ctrl01 and install crudini so we can change the config file from command line. ```bash [root@ctrl01 ~]# yum install -y crudini ``` Configure cinder to use storage as NFS as backup driver. ```bash echo "storage:/exports" > /etc/cinder/nfs_shares chown root:cinder /etc/cinder/nfs_shares chmod 0640 /etc/cinder/nfs_shares crudini --set /etc/cinder/cinder.conf DEFAULT nfs_shares_config /etc/cinder/nfs_shares crudini --set /etc/cinder/cinder.conf DEFAULT backup_driver cinder.backup.drivers.nfs ``` Enable SELinux policy ```bash [root@ctrl01 cinder(keystone_admin)]# setsebool -P virt_use_nfs on [root@ctrl01 cinder(keystone_admin)]# ssh compute01 setsebool -P virt_use_nfs on [root@ctrl01 cinder(keystone_admin)]# ssh compute02 setsebool -P virt_use_nfs on ``` Restart services and get list of the pools ```bash [root@ctrl01 cinder(keystone_admin)]# systemctl restart openstack-cinder-volume.service ``` :::info **Glance should be configured to use a Ceph RBD storage pool called "images"** ::: ***Solution*** Create a pool using the ceph utility. ```bash [root@workstation ~]# ceph osd pool create images 128 ``` Create a CephX user for glance ```bash [root@workstation-d4f5 ~(keystone_admin)]# ceph auth get-or-create client.glance mon 'allow r' osd 'allow class-read object_prefix rbd_children, allow rwx pool=images' -o /etc/ceph/ceph.client.glance.keyring ``` Copy the keyring file to ctrl01 ```bash [root@workstation ~]# scp /etc/ceph/ceph.client.glance.keyring root@ctrl01:/etc/ceph [root@workstation ~]# ssh root@ctrl01 chown root:glance /etc/ceph/ceph.client.glance.keyring [root@workstation ~]# ssh root@ctrl01 chmod 640 /etc/ceph/ceph.client.glance.keyring ``` Go to ctrl01 and config Glance. ```bash [root@workstation-d4f5 ~]# ssh ctrl01 ``` Configure glance-api.conf to use RBD ```bash crudini --set /etc/glance/glance-api.conf DEFAULT default_store rbd crudini --set /etc/glance/glance-api.conf glance_store default_store rbd crudini --set /etc/glance/glance-api.conf glance_store stores rbd crudini --set /etc/glance/glance-api.conf glance_store rbd_store_pool images crudini --set /etc/glance/glance-api.conf glance_store rbd_store_user glance crudini --set /etc/glance/glance-api.conf glance_store rbd_store_ceph_conf /etc/ceph/ceph.conf ``` Restart openstack-glance-api service ```bash [root@ctrl01 ~(keystone_admin)]# systemctl restart openstack-glance-api ``` :::info **OpenStack should be configured to use the Swift Cluster running on stor01 - stor03, NOT a local Swift AIO on the controller node** ::: ***Solution*** Already configured in keystone_user and swiftrc. :::info **SELinux should be enabled on all nodes** ::: ***Solution*** It's done by default. To verify, use the following command in workstation. ```bash [root@workstation-d4f5 ~]# for HOST in `grep 172 /etc/hosts | awk '{print $3}'`; do echo -e \\n----$HOST; ssh root@$HOST sestatus; done ```

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully