Ansible fails with /bin/sh: 1: /usr/bin/python: no

2020-01-27 09:26发布

I'm running into an error I've never seen before. Here is the command and the error:

$ ansible-playbook create_api.yml

PLAY [straw] ******************************************************************

GATHERING FACTS ***************************************************************
failed: [104.55.47.224] => {"failed": true, "parsed": false}
/bin/sh: 1: /usr/bin/python: not found


TASK: [typical | install required system packages] *****************************
FATAL: no hosts matched or all hosts have already failed -- aborting


PLAY RECAP ********************************************************************
           to retry, use: --limit @/Users/john/create_api.retry

104.55.47.224               : ok=0    changed=0    unreachable=0    failed=1

Here is the create_api.yml file:

---

- hosts: api
  remote_user: root
  roles:
    - api

And here is the hosts file:

[api]
104.55.47.224

I can remove the roles section and it won't make it to the first TASK, it will instead make it will only make it to the line /bin/sh: 1: /usr/bin/python: not found. What could be going on here?


NOTE: In case anyone is pinging the IP address and failing to get a response, you should know I've changed the IP address since pasting code.

EDIT python was installed locally, the problem was that it was not installed on the remote machine, which was running Ubuntu 15.04

19条回答
叛逆
2楼-- · 2020-01-27 09:55

I stumbled upon this error running ansible on Ubuntu 15.10 server, because it ships with Python 3.4.3 and ansible requires Python 2.

This is how my provision.yml looks now:

- hosts: my_app
  sudo: yes
  remote_user: root
  gather_facts: no
  pre_tasks:
    - name: 'install python2'
      raw: sudo apt-get -y install python

  tasks:
    - name: 'ensure user {{ project_name }} exists'
      user: name={{ project_name }} state=present
  • Don't forget the -y (says yes to all questions) option with apt-get (or raw module will get stuck silently)

  • gather_facts: no line is also critical (because we can't gather facts without python)

查看更多
Emotional °昔
3楼-- · 2020-01-27 09:56

THose using Packer may find below solution helpful

let's assume that you use ansible provisioner of packer, your config may look like below

you could install python using shell provisioner first then configure ansible_python_intepreter option as shown below

"provisioners": [
    {
      "type": "shell",
      "inline": [
        "apk update && apk add --no-cache python python-dev ansible bash"
      ]
    },
    {
      "type": "ansible-local",
      "playbook_file": "playbooks/your-play-book.yml",
      "playbook_dir": "playbooks",
      "extra_arguments": [
        "-e",
        "'ansible_python_interpreter=/usr/bin/python3'",
        "-vvv"
      ]
    },
查看更多
做自己的国王
4楼-- · 2020-01-27 09:56

You can indicate to Ubuntu 18.04 that you want to use python3 as the the first priority for /usr/bin/python.

- hosts: all
  become: true
  pre_tasks:
    - raw: update-alternatives --install /usr/bin/python python /usr/bin/python3 1
查看更多
对你真心纯属浪费
5楼-- · 2020-01-27 09:58

I personally found 3 possible solutions to this problem that work well in different situations:

Option 1 - Set ansible_python_interpreter: /usr/bin/python3 for hosts that have python3 installed by default

I think this is the superior method for solving the problem if you have a way to group your hosts by whether or not they have python3 installed by default. As far as I'm aware, python3 is available on all Ubuntu releases 16.04 and higher.

  • If all your hosts definitely have python3, you could add the variable to your group_vars/all.yml (or equivalent):
# group_vars/all.yml

ansible_python_interpreter: /usr/bin/python3
  • If some of your hosts don't have python3 and you have a way to tag them when using dynamic inventory (e.g. AWS tagging for ec2.py), you could apply the variable to certain hosts like this:
# group_vars/tag_OS_ubuntu1804.yml

ansible_python_interpreter: /usr/bin/python3
  • If you use static inventory and are able to group hosts based on whether they have python3, you could do something like this:
# inventory/hosts

[python2_hosts]
centos7_server

[python3_hosts]
u1804_server

[python3_hosts:vars]
ansible_python_interpreter=/usr/bin/python3

I like this option the most because it requires no changes on the remote host and only minor changes to variables, as opposed to options 2 and 3, which require additions to every playbook.

Option 2 - Install Python 2 using raw

This option requires putting a play at the top of every playbook with gather_facts: false that uses raw to install python:

- name: install python2 on all instances
  hosts: "*"
  gather_facts: false
  tasks:
    - name: run apt-get update and install python
      raw: "{{ item }}"
      loop:
        - sudo apt-get update
        - sudo apt-get -y install python
      become: true
      ignore_errors: true

ignore_errors: true is required if you plan to run the play on hosts that don't have apt-get installed (e.g. anything RHEL-based), otherwise they will error out in the first play.

This solution works, but is the lowest on my list for a few reasons:

  1. Needs to go at the top of every playbook (as opposed to option 1)
  2. Assumes apt is on the system and ignores errors (as opposed to option 3)
  3. apt-get commands are slow (as opposed to option 3)

Option 3 - Symlink /usr/bin/python -> /usr/bin/python3 using raw

I haven't seen this solution proposed by anyone else. It's not ideal, but I think it's superior to option 2 in a lot of ways. My suggestion is to use raw to run a shell command to symlink /usr/bin/python -> /usr/bin/python3 if python3 is on the system and python is not:

- name: symlink /usr/bin/python -> /usr/bin/python3
  hosts: "*"
  gather_facts: false
  tasks:
    - name: symlink /usr/bin/python -> /usr/bin/python3
      raw: |
        if [ -f /usr/bin/python3 ] && [ ! -f /usr/bin/python ]; then
          ln --symbolic /usr/bin/python3 /usr/bin/python; 
        fi
      become: true

This solution is similar to option 2 in that we need to put it at the top of every playbook, but I think it's superior in a few ways:

  • Only creates the symlink in the specific case that python3 is present and python is not -- it won't override Python 2 if it's already installed
  • Does not assume apt is installed
  • Can run against all hosts without any special error handling
  • Is super fast compared to anything with apt-get

Obviously if you need Python 2 installed at /usr/bin/python, this solution is a no go and option 2 is better.

Conclusion

  • I suggest using option 1 in all cases if you can.
  • I suggest using option 3 if your inventory is really large/complex and you have no way to easily group hosts with python3, making option 1 much more difficult and error-prone.
  • I only suggest option 2 over option 3 if you need Python 2 installed at /usr/bin/python.

Sources

查看更多
啃猪蹄的小仙女
6楼-- · 2020-01-27 10:00

Solution 1:

If you're using Ansible >2.2.0, you can set the ansible_python_interpreter configuration option to /usr/bin/python3:

ansible my_ubuntu_host -m ping -e 'ansible_python_interpreter=/usr/bin/python3'

or in your inventory file:

[ubuntu_hosts]
<xxx.xxx.xxx.xxx>

[ubuntu_hosts:vars]
ansible_python_interpreter=/usr/bin/python3

Solution 2:

If you're using Ansible <2.2.0 then you can add these pre_tasks to your playbook:

gather_facts: False
pre_tasks:
  - name: Install python for Ansible
    raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
    register: output
    changed_when: output.stdout != ""
    tags: always
  - setup: # aka gather_facts

UPDATE With ansible 2.8.x, you don't need to worry about it, it's working out of the box for python > 3.5 for both controller and target machine(s)

查看更多
乱世女痞
7楼-- · 2020-01-27 10:02

I was able to fix the same problem by installing Python on target machine i.e. the machine which we want to SSH to. I had used following command:

sudo apt-get install python-minimal
查看更多
登录 后发表回答