SSD Nodes Learn
Mwongozo Matt ConnorNa Matt Connor · Imeboreshwa 2026-07-25

Jifunze Ansible: playbook ya kwanza kwenye VPS

Sakinisha Ansible kwa pipx kwenye Ubuntu 24.04, andika inventory na playbook ya kwanza inayofanya VPS kuwa imara. Pata pia suluhisho la makosa ya Permission denied na sudo.

Unachojenga

Kompyuta moja ya udhibiti yenye Ansible imewekwa, na VPS moja au zaidi mpya za Ubuntu 24.04 ambazo hazina chochote ila picha ya kiwango. Mwishowe utakuwa na faili ya hesabu inayotaja seva zako, ping ya muda inayoonyesha uthibitishaji unafanya kazi kuanzia mwanzo hadi mwisho, na playbook inayoendesha orodha yote ya kazi za VPS mpya kama msimbo: mtumiaji wa upeleleji wenye ufunguo wako wa SSH, sshd iliyofanywa imara, fail2ban, masasisho yasiyohudumiwa, na ukuta wa ulinzi unaoruhusu OpenSSH kabla ya kukataa kila kitu kingine. Elekeza kwenye seva moja au ishirini. Iendeshe mara mbili na uendeshaji wa pili hautabadilisha chochote — hiyo ndio lengo kuu.

Baada ya miaka 15 ya kuandaa VPSes, naweza kukuambia muundo wa kweli: kila mtu anaweka seva tano za kwanza kwa mkono, kisha anapoteza wikendi moja kwenye seva ya sita kwa sababu hakuna anayekumbuka alifanya nini kwenye seva tano za kwanza. Mwongozo huu unachangia zaidi utafiti uliopo katika kusimamia seva nyingi za Linux — uchukue siku unapoona mwenyewe ukiandika apt install ile ile kwenye vituo vitatu.

Ansible kwa kweli ni nini, kwa aya moja

Ansible haitumii wakala. Hakuna daemon ya kusakinisha kwenye seva inazozisimamia: mashine ya udhibiti huungana kupitia SSH ya kawaida, inakopi moduli ndogo ya Python kwenye lengwa, kuitekeleza, kusoma JSON inayochapisha, na kufuta. Kitu pekee ambacho lengwa kinahitaji ni python3, ambacho kila picha ya kawaida ya Ubuntu tayari inacho. Neno muhimu ni idempotent, na lina maana wazi: kazi inaelezea hali, sio kitendo. state: present kwa kifurushi inamaanisha "hakikisha hiki kimesakinishwa", sio "endesha kifungashio". Ikiwa hali tayari inatimia, Ansible hagusi chochote na kinaripoti kama ok badala ya changed. Sifa hiyo ndiyo bidhaa nzima — ndiyo inayofanya uendeshaji tena wa playbook kuwa salama, na uendeshaji tena salama ndio unachobadilisha skripti ya shell kuwa miundombinu.

Masharti ya awali, na mabaya ya kwanza

  • Kompyuta ya udhibiti: kompyuta yako ndogo au VPS ndogo. Nadhani Ubuntu 24.04; macOS hufanya kazi sawa sawa mara pipx imewekwa kutoka Homebrew.
  • VPS moja au zaidi za lengo zikizindua Ubuntu 24.04 kwenye KVM, zinazopatikana kama root. Hakuna kitu kinachowekwa kwenye hizo.
  • Uthibitisho wa ufunguo wa SSH kwa kila lengo. Ansible huthibitishwa kwa kiwango sawa na amri yako ya ssh — kama ssh root@host inauliza neno siri, Ansible hushindwa.
  • Kwenye Ubuntu 24.04, pip install ansible huisha kwa error: externally-managed-environment. Hiyo ni sera makusudi ya usambazaji, sio uharibifu. Tumia pipx.
  • Nafasi ya YAML ni sehemu ya sintaksia. Mstari uliopotoshwa hutoa mapping values are not allowed in this context, na herufi ya tab popote ni hatari.
  • Endelea kuwa na kipindi cha SSH kinachofanya kazi kwenye kila lengo wakati playbook inaimarisha sshd. Kila kuzuiliwa nimesaidia mteja kurejea kutoka humo kulihusisha kufunga kipindi cha mwisho "ili kujaribu kwa upya".

Hatua ya 1: sakinisha Ansible kwenye mashine ya udhibiti kwa kutumia pipx, sio pip

Mwendo wa kawaida ni pip3 install ansible. Kwenye picha mpya kabisa ya 24.04 huo hufeli hatua moja mapema — Command 'pip3' not found, but can be installed with: sudo apt install python3-pip — na kusakinisha pip hukupeleka kwenye kizuizi halisi:

pip3 install ansible
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.

Ubuntu 24.04 huainisha Python ya mfumo kama inayodhibitiwa kutoka nje (PEP 668) kwa hivyo pip hawezi kushindana na apt juu ya faili zilezile. Usitumie --break-system-packages; jina la bendera hilo ni la kweli. Jibu sahihi ni pipx, ambayo huipa Ansible mazingira yake ya pekee yaliyotengwa na kuweka faili za executable kwenye PATH yako:

sudo apt update && sudo apt install -y pipx
pipx ensurepath
pipx install --include-deps ansible

Fungua kioleza kipya baada ya pipx ensurepath ili mabadiliko ya PATH yatekelezwe. --include-deps sio mapambo tu: kifurushi cha ansible hakileti hati zozote za console — ansible, ansible-playbook, na zilizobaki ni pointi za kuingia za tegemezi yake ya ansible-core — kwa hivyo bila bendera hiyo pipx huikataa usakinishaji kwa No apps associated with package ansible or its dependencies. Na sakinisha kifurushi cha ansible, sio ansible-core tupu — kifurushi kamili kinajumuisha mkusanyiko wa jamii, na playbook hii inatumia moduli kutoka kwenye viwili vya hivyo (ansible.posix na community.general).

ansible --version

Matokeo sahihi huanza na mstari kama ansible [core 2.19.x] na kuainisha Python inayoendeshwa chini yake; toleo lolote la sasa la core ni sawa kwa kila kitu hapa. ansible: command not found badala yake ina maana ~/.local/bin bado hayupo kwenye PATH yako — fungua kioleza kipya, au source ~/.bashrc.

Hiyo ni usakinishaji wote. Lengo halipokei chochote.

Hatua ya 2: Ufikiaji wa ufunguo wa SSH kwenye kila lengo

ssh-keygen -t ed25519 -C "ansible control"
ssh-copy-id root@10.0.0.10
ssh-copy-id root@10.0.0.20

Kisha uthibitishe, mara moja kwa kila mwenyeji:

ssh root@10.0.0.10 true && echo ok

Mstari huo mmoja unafanya kazi mbili: unathibitisha kwamba uthibitisho wa ufunguo unafanya kazi bila nywila, na unarekodi ufunguo wa mwenyeji kwenye known_hosts. Fanya hivi sasa, kwa sababu Ansible huonyesha ufunguo wa mwenyeji usiorekodiwa kama mwaliko wa kuingiliana uliofichwa katikati ya uendeshaji, ambao unaonekana kama kusimama kabisa.

Hatua ya 3: hesabu — INI kwanza, YAML inapokua

Hesabu ni faili la maandishi linaloorodhesha mashine ambazo Ansible inaweza kugusa. Unda inventory.ini katika saraka mpya ya mradi:

[vps]
web1 ansible_host=10.0.0.10
web2 ansible_host=10.0.0.20

[vps:vars]
ansible_user=root

web1 ni jina la dhana unalochagua — ndilo linaloonekana kwenye matokeo na unalolenga kwa kutumia --limit web1. ansible_host ni anwani halisi. [vps] ni kikundi, na [vps:vars] huweka viambatisho kwa kila mwenyeji ndani yake; ansible_user ni mtumiaji ambaye Ansible huingia kama yeye. Kando nayo, ansible.cfg ili usiandike -i tena:

[defaults]
inventory = inventory.ini

Ansible husoma ansible.cfg kutoka saraka inayotumika sasa. Hesabu hiyo hiyo katika YAML — ihifadhi kama inventory.yml na uelekeze ansible.cfg kwenye jina hilo badala yake — ndiyo utakayopendelea mara wenyeji wakibeba viambatisho kadhaa kila mmoja:

vps:
  hosts:
    web1:
      ansible_host: 10.0.0.10
    web2:
      ansible_host: 10.0.0.20
  vars:
    ansible_user: root

Zina sawa. INI ni rahisi kuangalia kwa macho kwa seva mbili; YAML inakua vizuri kwa ishirini. Chagua moja na acha kufikiria juu yake.

Hatua ya 4: amri za ad-hoc — pong ya kijani inayothibitisha kila kitu

ansible all -m ping

Hii si ICMP. Moduli ya ping ni majaribio kamili: kuingia kwa SSH, kunakili moduli, utekelezaji wa Python kwenye lengo, na usafishaji. Matokeo sahihi ni kijani, kizuizi kimoja kwa kila mwenyeji:

web1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

Kijani SUCCESS inamaanisha uthibitisho, kisomaji cha Python, na usafirishaji vyote vinafanya kazi — playbook pia itafanya kazi. Nyekundu UNREACHABLE! inamaanisha usafirishaji umeshindwa kabla ya moduli yoyote kutekelezwa; mfuho halisi na suluhisho viko katika sehemu ya hali za kushindwa hapa chini. Amri zaidi mbili za ad-hoc za kujua:

ansible all -a "uptime"
ansible all -m apt -a "update_cache=true upgrade=dist" --become

Ad-hoc kwa ajili ya shughuli za mara moja na ukaguzi. Kitu chochote unachotaka kukimbia mara mbili kinastahili kuwa kwenye playbook.

Hatua ya 5: playbook ya kwanza — orodha ya ukaguzi wa VPS mpya kama msimbo

Hii ni kila kitu ambacho ungefanya kwa mkono katika dakika kumi za kwanza kwenye seva mpya. Hifadhi kama site.yml:

---
- name: Baseline a fresh Ubuntu VPS
  hosts: vps
  become: true

  vars:
    deploy_user: deploy
    deploy_pubkey: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
    baseline_packages:
      - fail2ban
      - unattended-upgrades
      - ufw
    baseline_services:
      - fail2ban
      - unattended-upgrades

  tasks:
    - name: Create the deploy user
      ansible.builtin.user:
        name: "{{ deploy_user }}"
        groups: sudo
        append: true
        shell: /bin/bash

    - name: Install the deploy user's SSH key
      ansible.posix.authorized_key:
        user: "{{ deploy_user }}"
        key: "{{ deploy_pubkey }}"

    - name: Passwordless sudo for the deploy user
      ansible.builtin.copy:
        dest: /etc/sudoers.d/deploy
        content: "{{ deploy_user }} ALL=(ALL) NOPASSWD:ALL\n"
        mode: "0440"
        validate: /usr/sbin/visudo -cf %s

    - name: Install baseline packages
      ansible.builtin.apt:
        name: "{{ baseline_packages }}"
        state: present
        update_cache: true

    - name: Enable and start baseline services
      ansible.builtin.service:
        name: "{{ item }}"
        state: started
        enabled: true
      loop: "{{ baseline_services }}"

    - name: Harden sshd with a drop-in
      ansible.builtin.copy:
        dest: /etc/ssh/sshd_config.d/00-hardening.conf
        content: |
          PasswordAuthentication no
          KbdInteractiveAuthentication no
          PermitRootLogin prohibit-password
          X11Forwarding no
        mode: "0644"
        validate: /usr/sbin/sshd -t -f %s
      notify: Restart ssh

    - name: Allow OpenSSH through ufw
      community.general.ufw:
        rule: allow
        name: OpenSSH

    - name: Enable ufw with default deny
      community.general.ufw:
        state: enabled
        policy: deny

  handlers:
    - name: Restart ssh
      ansible.builtin.service:
        name: ssh
        state: restarted

Mistari inayofaa kuelewa badala ya kuigiza tu:

Vitambulishi viko chini ya vars: na vinarejeliwa kwa "{{ deploy_user }}" — weka nukuu kwenye msema mzima wakati thamani inaanza na mabano ya wima, la sivyo kichanganuzi cha YAML kitaki tafsiri. Kipengele cha lookup('file', ...) kinasoma ufunguo wako wa umma kutoka kwenye mashine ya udhibiti wakati wa utekelezaji, hivyo playbook haibebi nyenzo za ufunguo.

Kitanzi. loop: "{{ baseline_services }}" kinatekeleza kazi ya huduma mara moja kwa kila kipengele, na matokeo yanaonyesha kila kipengele kwenye mstari wake. Angalia kwamba kazi ya apt inachukua orodha kamili ya pakiti mara moja badala yake — muamala mmoja wa apt ni wa haraka zaidi na ndio mtindo unaopendelewa kwa pakiti; vitanzi vimebaki kwa moduli zinazofanya kazi kwa kipengele kimoja wakati mmoja kwa kweli.

Mshughulikiaji (handler) ndio dhana ya kuelewa kwa kina. notify: Restart ssh hakumaanishi "anza ssh upya sasa". Kinaweka mshughulikiaji kwenye foleni, ambao hutekelezwa mara moja mwishoni mwa uchezaji, na tu ikiwa kazi inayotangaza imeripoti changed. Tekeleza playbook tena kesho: faili la drop-in ni sahihi tayari, kazi ya kunakili inaripoti ok, na sshd hairudishwi kamwe. Mstari wa validate: ni kifungo cha usalama kwenye kichocheo — sshd hukagua faili kabla ya kubadilisha cha zamani, hivyo makosa ya kuandika hupelekea kazi kushindwa badala ya kuharibu daemon.

PermitRootLogin prohibit-password, si no — kusudiwa. Playbook hii ingia kama root kwa kutumia ufunguo. prohibit-password kinafunga nguvu neno la root huku yako ikiwa haijawekwa. Mara mtumiaji wa upelekaji anapothibitishwa (ssh deploy@10.0.0.10 sudo true — anwani kamili, kwani web1 ni jina la pekee linalojulikana na Ansible pekee), badilisha ansible_user=deploy kwenye hesabu na uifanye imara kwa no katika utekelezaji unaofuata. Fanya usalama imara kwa mpangilio usioweza kukufunga nje.

Kiambatisho cha 00- ni muhimu. Kwa maneno mengi ya msingi, sshd huzingatia tukio la kwanza analolisoma, na sshd_config ya Ubuntu inajumuisha sshd_config.d/*.conf kwa mpangilio wa herufi kabla ya mwili wake. Picha za wingu za Ubuntu 24.04 tayari zina 60-cloudimg-settings.conf katika saraka hiyo, na watoa huduma wanaowasha nguvu neno kupitia cloud-init wanaongeza 50-cloud-init.conf yenye PasswordAuthentication yes; kuitaja yetu 00-hardening.conf kinaifanya ipangwe kwanza na kushinda zote mbili.

Mpangilio wa kazi ndio usalama wa firewall. Allow OpenSSH inatekelezwa kabla ya Enable ufw yenye sera ya kukataa — Ansible hutekeleza kazi kwa mpangilio halisi ulioorodheshwa, hivyo tundu lipo kabla ya ukuta kujengwa. fail2ban haitaji usanidi wowote ili kuwa na manufaa hapa; chaguo-msingi chake cha Ubuntu kinafuatilia sshd moja kwa moja, na kile ambacho magereza hufanya kwa halisi — na cha kurekebisha — kimeelezwa katika mwongozo wa fail2ban kwenye Ubuntu 24.04.

Hatua ya 6: jaribio lisilobadilisha kwa kutumia --check, kisha tekeleza kwa kweli

ansible-playbook site.yml --check

Hali ya ukaguzi huunganisha, inahesabu kile ambacho kingefanywa, na habadilishi chochote. Soma idadi ya changed= katika PLAY RECAP sehemu ya chini — hiyo ni idadi ya kazi ambazo zingebadilisha kila mwenyeji. Onyo moja la wazi: hali ya ukaguzi ina kikwazo cha kimuundo popote ambapo kazi ya baadaye inategemea mabadiliko ya kazi ya awali. Picha sanifu ya seva ya Ubuntu inakuja tayari na ufw, hivyo playbook hii inaendeshwa kwa usafi katika hali ya ukaguzi — lakini kwenye picha ndogo isiyo na ufw, kazi za ufw hufeli katika hali ya ukaguzi, kwa sababu hali ya ukaguzi haijawahi kufakika kusakinisha kifurushi na moduli kisha haina cha kuita. Hiyo ni kikwazo cha majaribio yasiyobadilisha, sio hitilafu katika playbook yako. Harakati hii inakubalika:

ansible-playbook site.yml

Kila kazi inachapisha mstari kwa kila mwenyeji — manjano changed, kijani ok — na muhtasari unapaswa kusomeka:

PLAY RECAP *********************************************************************
web1 : ok=10  changed=9  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0
web2 : ok=10  changed=9  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

Kumi ok ni ukusanyaji wa ukweli pamoja na kazi nane pamoja na kihandleri. changed yako inaruhusiwa kutofautiana na yangu kwa moja au mbili: picha sanifu ya Ubuntu inakuja tayari na ufw na unattended-upgrades, na fail2ban hujianza wakati apt inakisakinisha, hivyo kazi inaweza kuripoti ok kwa halali katika uendeshaji wake wa kwanza — hali inayotangaza tayari imeshikiliwa. Nambari ambazo lazima ziwe sifuri ni unreachable na failed. Maelezo moju kuhusu become: true: ni utaratibu tu unapoingia kama root, lakini mara unapobadilisha ansible_user kuwa deploy, sudo ni halisi — na faili ya sudoers ya NOPASSWD ambayo playbook hii inasakinisha ndiyo inayoshikilia -K mbali na mstari wako wa amri. Bila hiyo unapata Missing sudo password, iliyoelezwa hapa chini.

Hatua ya 7: iendeshe mara mbili — jinsi idempotensi inavyoonekana

Endesha amri hiyo hiyo tena mara moja:

web1 : ok=9  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

changed=0, na ok imepungua kwa moja kwa sababu handler ambaye hajafanyiwa kazi hakuwahi kuendeshwa. Hakuna kitu kilichowekwa upya, sshd haukuanzishwa tena, ufw haukuguswa. Hii ndiyo inayofanya playbook iwe ukaguzi kiasi cha kuwa mratibu: ongeza web3 kwenye hesabu mwezi ujao na uendeshe tena — seva mpya inajengwa, seva za zamani zinakaguliwa. changed isiyo sifuri kwenye seva ambayo hujagusu ni mabadiliko ya moja kwa moja, na inakuambia mtu alihariri kwa mkono kile ambacho kilipaswa kuhaririwa ndani ya playbook.

Kutoka hapa mtindo huo unajipanga. Playbook inayofuata inayostahili kuandikwa inaanzisha WireGuard VPN kwenye VPS hiyo hiyo na inafanya kanuni ya ufw kuwa ngumu ili SSH ijibu tu kwenye handaki; baada ya hapo, playbook nyingine inayoweka Docker na Compose kwenye kila seva ya programu. Wakati site.yml inapozidi kuruka skrini tatu, igawe katika majukumu — lakini sio kabla ya hapo.

Aina za hitilafu, na mistari utakayoona

UNREACHABLE pamoja na Permission denied.

web1 | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: root@10.0.0.10: Permission denied (publickey).",
    "unreachable": true
}

Usafirishaji wa SSH umefeli kabla ya moduli yoyote kuanza kufanya kazi: ansible_user si sahihi, ufunguo haujahamishiwa kwenye mwenyeji huo, au ufunguo mbaya unatolewa. Rudia hili kwa kutumia ssh root@10.0.0.10 tu, kisha ssh -v kuona ni funguo zipi zilizotolewa. Ikiwa kuingia kwa SSH kwa nywila kinafanya kazi lakini Ansible haina, uliruka ssh-copy-id.

Nywila ya sudo haipo.

web1 | FAILED! => {
    "msg": "Missing sudo password"
}

Uliweka become: true, uliunganisha kama mtumiaji asiye root, na mtumiaji huyo anahitaji nywila kwa sudo. Ongeza -K (--ask-become-pass) kwenye mstari wa amri, au mpe mtumiaji ingizo la NOPASSWD kwenye sudoers — ambayo ndiyo sababu hasa playbook inasakaza moja kwa deploy kabla hujabadilisha kwenda kwayo.

error: externally-managed-environment. Uliendesha pip kwenye Python ya mfumo kwenye Ubuntu 24.04. Hili limefunuliwa katika hatua ya 1: pipx, sio pip, na sio --break-system-packages.

mapping values are not allowed in this context.

ERROR! Syntax Error while loading YAML.
  mapping values are not allowed in this context

Takriban daima ni suala la nafasi: ufunguo uko katika kina kisicho sahihi, au nafasi inakosa baada ya koloni. Nambari ya mstari iliyoripotiwa inaashiria karibu na kosa, sio kwenye kosa lenyewe — angalia pia mstari ulio juu yake. Jamaa yake found character '\t' that cannot start any token inaashiria kwamba tab imejitokeza; YAML hairuhusu tab. Fanya ansible-playbook site.yml --syntax-check kuwa tabia kabla ya kila uendeshaji, na weka kihariri chako kuwa na nafasi mbili za kuingiza kwa YAML.

/usr/bin/python3: not found. Hali adimu kwenye picha za kawaida za Ubuntu 24.04, lakini ya kawaida kwenye zile ndogo au za netboot: utekelezaji wa moduli unafeli kwa sababu lengo halina Python. Anzisha hili kwa kutumia moduli ya raw, ambayo ndiyo moduli pekee inayohitaji chochote upande wa mbali: ansible all -m raw -a "apt-get update && apt-get install -y python3" --become, kisha uendeshe playbook tena.

FAQ

Je, ninahitaji kusakinisha Ansible kwenye seva inazozisimamia?

Hapana. Ansible haitumii wakala: mashine ya udhibiti inasukuma moduli ndogo za Python kupitia SSH, kuziendesha, na kuziondoa. Lengo linahitaji tu python3 na ufikiaji wa SSH, ambayo vyote tayari vipo kwenye picha za kawaida za Ubuntu. Usakinishaji pekee katika mwongozo huu mzima unafanyika kwenye mashine yako ya udhibiti.

Kwa nini Ansible inasema "Permission denied (publickey)"?

Kizuizi cha UNREACHABLE! chenye Permission denied (publickey) kinamaanisha uthibitisho wa SSH umeshindwa kabla Ansible hajaendesha chochote. Hakikisha kwamba ansible_user katika hesabu inalingana na akaunti uliyoiandaa kwa kweli, kwamba uliendesha ssh-copy-id kwenda kwenye mwenyeji huo, na kwamba ssh user@host ya kawaida inakuingiza bila nywila. Lolote linalorekebisha amri ya kawaida ya ssh linarekebisha Ansible, kwa sababu zinatumia njia sawa ya usafirishaji.

Nini maana ya idempotent katika Ansible?

Kazi inatangaza hali inayotakikana — "kifurushi hiki kipo", "mstari huu uko katika faili hii" — badala ya kitendo cha kufanya. Ikiwa hali tayari inatimia, Ansible haifanyi chochote na inaripoti ok badala ya changed. Ndiyo sababu kuendesha playbook mara mbili inaonyesha changed=0 mara ya pili, na sababu kuendesha tena ni ukaguzi salama badala ya usakinishaji upya wenye hatari.

Je, nitumie pip au pipx kusakinisha Ansible kwenye Ubuntu 24.04?

pipx. Ubuntu 24.04 inaainisha Python ya mfumo kama inayosimamiwa kutoka nje, kwa hivyo pip install ansible inashindwa kwa error: externally-managed-environment kwa makusudi. pipx install --include-deps ansible inaweka Ansible kwenye virtualenv iliyotengwa na inatoa ansible, ansible-playbook, na zilizobaki kwenye PATH yako kwa urahisi.

Tofauti kati ya pakiti za ansible na ansible-core ni ipi?

ansible-core ni injini pamoja na moduli za ansible.builtin tu. Pakiti ya ansible inaunganisha core na mkusanyiko wa jamii uliochaguliwa — ikiwa ni pamoja na ansible.posix (moduli ya authorized_key) na community.general (moduli ya ufw), ambazo zote zinatumika katika mwongozo huu. Anza na pakiti kamili; punguza hadi core pamoja na mikusanyiko iliyochaguliwa kwa mkono tu ukiwa na sababu ya kufanya hivyo.