Snap install from bash script error

Hello,

I can happily install snaps in lxc from the commandline with:

lxc launch ubuntu:16.04 test-bash-script
lxc exec test-bash-script -- sudo snap install hello

but if I put the same into a bash script, I receive an error:

Creating test-bash-script
Starting test-bash-script
sudo: unable to resolve host test-bash-script: Connection refused
error: cannot communicate with server: Post http://localhost/v2/snaps/hello: dial unix /run/snapd.socket: connect: no such file or directory

Can anybody please point me in the right direction?

Hi!

Most likely your script is too fast for LXD and the second command (lxc exec) is running while the container has not been fully launched.

Specifically, when you lxc launch a new container, the container is initialized and is booted up. As soon as it is booted up, lxc launch returns back to your shell. But it takes a couple of seconds for the container to actually complete the booting process so that it becomes fully usable. It makes sense of LXD to work that way, because as soon as the container is starting to boot up, it is independent from LXD; it stands on its own. I think it would not fit for LXD to include a feature to wait until the container’s init has completed.

For example, the Ubuntu containers automatically create a non-root ubuntu account. This is created using the cloud-init feature, and typically the account is created towards the end of the booting process. If you launch a container and very quickly start a shell as ubuntu in this container, you may see an error that there is no ubuntu user. This user account was just not created just yet.

Back to your script now. You need some recipe that would wait until the container is fully booted before you start using it.

One option is to run sleep 5 to wait for five seconds. Very easy, least effort, but may not work all the time.

The elegant option is to lxc exec the following command systemctl is-system-running --wait in the container. This will wait until the init (systemd) has completed booting up. Unfortunately, the --wait is only available in much recent systemd (does not work with Ubuntu 16.04 or Ubuntu 18.04, should work with Ubuntu 19.04 or newer).

In the LTS versions of Ubuntu, you can write a script to wait until the system is running, simply by polling the output of systemctl is-system-running.

Here is a sample script,

lxc launch ubuntu:18.04 u18
sleep 1                    # Give a chance for systemd to start running.
lxc exec u18 systemctl is-system-running
lxc exec u18 -- bash -c 'while [ "$(systemctl is-system-running)" != "running" ]&& [ "$(systemctl is-system-running)" != "degraded" ]; do sleep 1; done'
lxc exec u18 systemctl is-system-running
lxc exec u18 -- sudo snap install hello

The container has finished booting up when it gets into the running state. Or degraded, which happens when some service has failed. In the containers there are some services that fail in any case because they cannot work yet in a container yet, so you are stuck with degraded.

Here is some sample output.

$ bash myscript 
Creating u18
Starting u18
initializing                   
degraded
2019-08-07T10:25:49Z INFO Waiting for restart...
hello 2.10 from Canonical✓ installed

A brilliant answer, thank you @simos ! Tested and your sample script works as expected for me on 16.04.

Thanks @aaron!

I wrote a blog post on this with a bit more background.