Proxy device not connecting to Pulseaudio on LXD host

I am following Simos’ blog post Running X11 software in LXD containers – Mi blog lah! and audio does not work.

LXD 4.0.0 (Edge)

The LXC profile is a copy/ paste from Simos’ post.

config:
  environment.DISPLAY: :0
  environment.PULSE_SERVER: unix:/home/ubuntu/pulse-native
  nvidia.driver.capabilities: all
  nvidia.runtime: "true"
  user.user-data: |
    #cloud-config
    runcmd:
      - 'sed -i "s/; enable-shm = yes/enable-shm = no/g" /etc/pulse/client.conf'
    packages:
      - x11-apps
      - mesa-utils
      - pulseaudio
description: GUI LXD profile
devices:
  PASocket1:
    bind: container
    connect: unix:/run/user/1000/pulse/native
    listen: unix:/home/ubuntu/pulse-native
    security.gid: "1000"
    security.uid: "1000"
    uid: "1000"
    gid: "1000"
    mode: "0777"
    type: proxy
  X0:
    bind: container
    connect: unix:@/tmp/.X11-unix/X1
    listen: unix:@/tmp/.X11-unix/X0
    security.gid: "1000"
    security.uid: "1000"
    type: proxy
  mygpu:
    type: gpu
name: x11
used_by: []

Running pactl info returns Connection refused.

ubuntu@firefox:~$ pactl info
Connection failure: Connection refused
pa_context_connect() failed: Connection refused

The container log contains

$ lxc info --show-log local:firefox 
Name: firefox
Location: none
Remote: unix://
Architecture: x86_64
Created: 2020/04/18 02:52 UTC
Status: Running
Type: container
Profiles: default, x11
Pid: 27344
Ips:
  eth0:	inet	10.67.174.95	veth45940b54
  eth0:	inet6	fd42:e196:7303:cb4f:216:3eff:fea9:2457	veth45940b54
  eth0:	inet6	fe80::216:3eff:fea9:2457	veth45940b54
  lo:	inet	127.0.0.1
  lo:	inet6	::1
Resources:
  Processes: 57
  Disk usage:
    root: 184.80MB
  CPU usage:
    CPU usage (in seconds): 43
  Memory usage:
    Memory (current): 316.76MB
  Network usage:
    eth0:
      Bytes received: 48.30MB
      Bytes sent: 531.74kB
      Packets received: 12206
      Packets sent: 6569
    lo:
      Bytes received: 4.22kB
      Bytes sent: 4.22kB
      Packets received: 46
      Packets sent: 46

Log:

lxc firefox 20200418025248.733 ERROR    cgfsng - cgroups/cgfsng.c:mkdir_eexist_on_last:1151 - File exists - Failed to create directory "/sys/fs/cgroup/cpuset//lxc.monitor.firefox"
lxc firefox 20200418025248.733 ERROR    cgfsng - cgroups/cgfsng.c:mkdir_eexist_on_last:1151 - File exists - Failed to create directory "/sys/fs/cgroup/cpuset//lxc.payload.firefox"
lxc firefox 20200418025248.734 ERROR    utils - utils.c:lxc_can_use_pidfd:1855 - Invalid argument - Kernel does not support waiting on processes through pidfds
lxc firefox 20200418025248.738 WARN     cgfsng - cgroups/cgfsng.c:fchowmodat:1569 - No such file or directory - Failed to fchownat(17, memory.oom.group, 1000000000, 0, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW )

The error above says the /sys/fs/cgroup/cpuset//lxc.monitor.firefox file exists already. However, this file does not exist. Not sure whether the double slash is causing this problem? Doing ls on the /sys/fs/cgroup/cpuset/ directory shows the following.

ubuntu@firefox:~$ ls -lah /sys/fs/cgroup/cpuset/
total 0
drwxrwxr-x  2 nobody root      0 Apr 18 03:49 .
drwxr-xr-x 15 root   root    380 Apr 18 03:49 ..
-rw-r--r--  1 nobody nogroup   0 Apr 18 03:49 cgroup.clone_children
-rw-rw-r--  1 nobody root      0 Apr 18 03:55 cgroup.procs
-rw-r--r--  1 nobody nogroup   0 Apr 18 03:55 cpuset.cpu_exclusive
-rw-r--r--  1 nobody nogroup   0 Apr 18 03:49 cpuset.cpus
-r--r--r--  1 nobody nogroup   0 Apr 18 03:55 cpuset.effective_cpus
-r--r--r--  1 nobody nogroup   0 Apr 18 03:55 cpuset.effective_mems
-rw-r--r--  1 nobody nogroup   0 Apr 18 03:55 cpuset.mem_exclusive
-rw-r--r--  1 nobody nogroup   0 Apr 18 03:55 cpuset.mem_hardwall
-rw-r--r--  1 nobody nogroup   0 Apr 18 03:55 cpuset.memory_migrate
-r--r--r--  1 nobody nogroup   0 Apr 18 03:55 cpuset.memory_pressure
-rw-r--r--  1 nobody nogroup   0 Apr 18 03:55 cpuset.memory_spread_page
-rw-r--r--  1 nobody nogroup   0 Apr 18 03:55 cpuset.memory_spread_slab
-rw-r--r--  1 nobody nogroup   0 Apr 18 03:49 cpuset.mems
-rw-r--r--  1 nobody nogroup   0 Apr 18 03:55 cpuset.sched_load_balance
-rw-r--r--  1 nobody nogroup   0 Apr 18 03:55 cpuset.sched_relax_domain_level
-rw-r--r--  1 nobody nogroup   0 Apr 18 03:55 notify_on_release
-rw-rw-r--  1 nobody root      0 Apr 18 03:49 tasks

By the way, the audio is working on the LXD host (my regular laptop).

$ pactl info
Server String: /run/user/1000/pulse/native
Library Protocol Version: 33
Server Protocol Version: 33
Is Local: yes
Client Index: 24
Tile Size: 65472
User Name: myusername
Host Name: myhostname
Server Name: pulseaudio
Server Version: 13.0
Default Sample Specification: s16le 2ch 44100Hz
Default Channel Map: front-left,front-right
Default Sink: alsa_output.usb-0b0e_Jabra_Link_370_745C4BE44B24-00.analog-stereo
Default Source: alsa_input.usb-0b0e_Jabra_Link_370_745C4BE44B24-00.mono-fallback
Cookie: c778:bfaa

I also tried with an LXD disk device instead but it showed the same connection refused error. I am wondering whether this is an LXD/ LXC issue or a problem with my laptop… BTW, I got Virtualbox running on my laptop too and there the Audio is not working. For example, my Windows virtual machine cannot find the device although I believe Virtualbox is configured correctly.

How can I troubleshoot this further?

Hi Hagen!

To troubleshoot, you would need to verify that the proxy device for the PulseAudio socket has been created successfully.

First, let’s check the PulseAudio socket by running the following (on the host). The socket exists, and it is owned by our UID/GID (account myusername in my case).

$ ls -l /var/run/user/1000/pulse/native 
srw-rw-rw- 1 myusername myusername 0 Apr  18 11:29 /var/run/myusername/1000/pulse/native
$ 

Then, check the numeric UID/GID of our account on the host. The numeric value is 1000 for both the UID and GID. Therefore, the security.uid: 1000 and security.gid: 1000 values in the proxy device for the PulseAudio socket in the profile, are correct.

$ id
uid=1000(myusername) gid=1000(myusername) groups=1000(myusername),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare)
$ 

As far as I understand, you have created a ubuntu:18.04 container, therefore the values for uid: 1000 and gid: 1000 are correct as well. These are same for everyone.

The last part to check in the container, is that the /home/ubuntu/pulse-native socket exists in the container.

ubuntu@mycontainer:~$ ls -l /home/ubuntu/pulse-native 
srwxrwxrwx 1 ubuntu ubuntu 0 Apr 18 08:22 /home/ubuntu/pulse-native
ubuntu@mycontainer:~$ 

Now, when the container is created with a command line like the following, it applies the x11 profile. The container image has to be a cloud-init enabled container image. That currently means any container in ubuntu: and only the images:.../cloud container images (cloud means cloud-init support).

lxc launch ubuntu:18.04 mycontainer --profile default --profile x11

When the container is created, you get the following cloud-init command to run in the container to make PulseAudio use a Unix socket. Therefore, get into the container and verify that the change has indeed been applied.

sed -i "s/; enable-shm = yes/enable-shm = no/g" /etc/pulse/client.conf

Finally, there are many ways to get a shell into a container. I wrote about this at https://blog.simos.info/using-command-aliases-in-lxd-to-exec-a-shell/
Obviously, you should be using the non-root (ubuntu) command in Ubuntu containers, otherwise you would need to adapt the LXD profile to the other container images.

When you launch such a GUI container, it takes a few tens of seconds to run the full cloud-init instructions, to install those packages and return control back to the container. The environment variable PULSE_SERVER might not get applied fast enough if you get a shell too quickly. Therefore, verify with

ubuntu@mycontainer:~$ echo $PULSE_SERVER 
unix:/home/ubuntu/pulse-native
ubuntu@mycontainer:~$ 

If all these work fine and you still cannot get pactl info to work in the container, then things will get more interesting.

1 Like

Hi Simos,

Thank you for the very detailed response.

It turned out to be an issue with the environment variables.
The LXC profile does not appear to take into account the environment settings for both variables.

config:
  environment.DISPLAY: :0
  environment.PULSE_SERVER: unix:/home/ubuntu/pulse-native

Though, graphics work without having the DISPLAY variable in place (I guess since it defaults to 0). My impression is that this might be a bug?

To work around it I have amended the LXC profile by adding write_files to the Cloud Init.

config:
  environment.DISPLAY: :0
  environment.PULSE_SERVER: unix:/home/ubuntu/pulse-native
  nvidia.driver.capabilities: all
  nvidia.runtime: "true"
  user.user-data: |
    #cloud-config
    runcmd:
      - 'sed -i "s/; enable-shm = yes/enable-shm = no/g" /etc/pulse/client.conf'
    packages:
      - x11-apps
      - mesa-utils
      - pulseaudio
    write_files:
      - owner: root:root
        permissions: '0644'
        append: true
        content: |
          PULSE_SERVER=unix:/home/ubuntu/pulse-native
        path: /etc/environment
description: GUI LXD profile
devices:
  PASocket1:
    bind: container
    connect: unix:/run/user/1000/pulse/native
    listen: unix:/home/ubuntu/pulse-native
    security.gid: "1000"
    security.uid: "1000"
    uid: "1000"
    gid: "1000"
    mode: "0777"
    type: proxy
  X0:
    bind: container
    connect: unix:@/tmp/.X11-unix/X1
    listen: unix:@/tmp/.X11-unix/X0
    security.gid: "1000"
    security.uid: "1000"
    type: proxy
  mygpu:
    type: gpu
name: x11
used_by: []

Hi Simos,

I have got a slightly confusing update to make. You mentioned that the Shell might not be ready and when logging into the container too early the environment variables are not around yet.

I previously tested this by launching the container and waiting for about 8 minutes. Then I logged into the machine with lxc exec firefox -- sudo --user ubuntu --login and the PULSE_SERVER did not exist. The same, I believe, was the case with the DISPLAY variable.

However, I have just now tried to echo $DISPLAY on one of my testing containers (only waited a couple of seconds to login) and the $DISPLAY variable existed with the correct value.

By the way, I currently perceive my LXD experience as flaky as I seemingly erratically get the below error.

lxc launch ubuntu:18.04 --profile default --profile x11 firefox2
Creating firefox2
Starting firefox2
Error: Error occurred when starting proxy device: Error: Failed to listen on /home/ubuntu/pulse-native: listen unix /home/ubuntu/pulse-native: bind: no such file or directory
Try `lxc info --show-log local:firefox2` for more info

I will create another post for this.

I can confirm precisely the same errors. I can also confirm that quater’s workaround is effective.

I have also effectively worked around the problem by splitting the profile into two pieces with the X11 component following Simos’s original profile and the PulseAudio component following an earlier profile created by Simos in which PulseAudio is defined as a disk device rather than a proxy:

X11 component:

config:
  environment.DISPLAY: :0
  user.user-data: |
    #cloud-config
    packages:
      - x11-apps
      - mesa-utils
description: GUI LXD profile
devices:
  X0:
    bind: container
    connect: unix:@/tmp/.X11-unix/X0
    listen: unix:@/tmp/.X11-unix/X0
    security.gid: "1000"
    security.uid: "1000"
    type: proxy
  mygpu:
    type: gpu
name: x11
used_by: []

pa component:

config:
  raw.idmap: "both 1000 1000"
  user.user-data: |
    #cloud-config
    runcmd:
      - 'sed -i "s/; enable-shm = yes/enable-shm = no/g" /etc/pulse/client.conf'
      - 'echo export PULSE_SERVER=unix:/tmp/.pulse-native | tee --append /home/ubuntu/.profile'
    packages:
      - pulseaudio
description: pulseaudio LXD profile
devices:
  PASocket:
    path: /tmp/.pulse-native
    source: /run/user/1000/pulse/native
    type: disk
name: pa
used_by:

Containers are launched by combining both profiles along with default. Both methods get the job done, but I prefer quater’s as being more elegant.

1 Like

Hi!

When a container is being created, the last part is always the cloud-init. That is, LXD starts the container, and when the container takes over, does the startup and when it reaches the cloud-init service, it performs the instructions that that were gived in the LXD profile.
You can know whether the cloud-init instructions have been processed, see How to know when a LXD container has finished starting up – Mi blog lah! (and the comments).

When you setup environment.PULSE_SERVER in the LXD profile, you will notice that this is not added to /etc/environment or somewhere else. So, how does it propagate into a shell in the container?
My old tutorials had a LXD profile that would edit ~/.bashrc to add this variable. This was good enough for lxc exec mycontainer -- sudo --user ubuntu --login, or frankly in any case you get a shell.
But the elegant way is to use environment.PULSE_SERVER, which means that is needs a special way to get the shell.
See Using command aliases in LXD to exec a shell – Mi blog lah! and specifically the last two ways (I use the last way), which manages to get any environment variables from LXD.

The same error happened to me, the duckhook solution solved the problem.

Does anyone found solution to this (excluding workaround)?

Hi,
Sorry to “bump” this thread but I really need to comment because I spend so many hours to make pulseaudio to work.

First things first, I use LXD 4.17 without snap on manjaro.

For me Simos’ blog post does not work for me for two reasons :

  • LXC container refuse to start because /home/ubuntu/pulse-native does not exist. Yes, /home/ubuntu exists after a little bit of time but it’s too late.
  • environment variable PULSE_SERVER is not set in the container

The @duckhook does not for for me for two reasons :

  • Only the last cloud-init is executed in the container, so if you split profile, you will miss packages
  • Use type disk do not work. (maybe also because of tmp)

For me, some modifications to the original Simos are required

  • @quarter workaround is still required
  • Use type bind seems required but should not be put in /home/ubuntu (does not exist), /tmp (will be override at container boot), or in /var/run (permission denied)

So here is my solution, putting pulse socket in /var is not sexy, but it works:

config:
  environment.DISPLAY: :0
  environment.PULSE_SERVER: unix:/var/pulse-native
  user.user-data: |
    #cloud-config
    runcmd:
      - 'sed -i "s/; enable-shm = yes/enable-shm = no/g" /etc/pulse/client.conf'
    packages:
      - x11-apps
      - mesa-utils
      - pulseaudio
    write_files:
      - owner: root:root
        permissions: '0644'
        append: true
        content: |
          PULSE_SERVER=unix:/var/pulse-native
        path: /etc/environment
description: GUI LXD profile
devices:
  PASocket1:
    bind: container
    connect: unix:/run/user/1000/pulse/native
    listen: unix:/var/pulse-native
    security.gid: "1000"
    security.uid: "1000"
    uid: "1000"
    gid: "1000"
    mode: "0777"
    type: proxy
  X0:
    bind: container
    connect: unix:@/tmp/.X11-unix/X1
    listen: unix:@/tmp/.X11-unix/X0
    security.gid: "1000"
    security.uid: "1000"
    type: proxy
  mygpu:
    type: gpu
name: x11
used_by: []

You might also take a look at:

Two years on from my earlier post and LXD has changed to the point that both the original simos implementation and my earlier two-part workaround are no longer effective. I have found that, on Impish, only Inglebard’s solution works. I suspect the same as Inglebard: that when cloud-config tries to write PULSE_SERVER, /home/ubuntu is not yet initialized, so the proxy has nothing to bind to. By placing the pulse socket in /var (which is a system level directory) it exists early enough in the init process that the pulse socket can find an existent directory.

The only problem with Inglebard’s config file is a typo which breaks the video device. X0 is instructed to connect to X1 display but told to listen on X0 display. The proper lines should be:

    connect: unix:@/tmp/.X11-unix/X0
    listen: unix:@/tmp/.X11-unix/X0

…or, if display is actually on X1, then both should be X1